W3C FileAPI in Firefox 3.6

Often, web applications will prompt the user to select a file, typically to upload to a server. Unless the web application makes use of a plugin, file selection occurs through an HTML input element, of the sort <input type="file"/>. Firefox 3.6 now supports much of the W3C File API, which specifies the ability to asynchronously read the selected file into memory, and perform operations on the file data within the web application (for example, to display a thumbnail preview of an image, before it is uploaded, or to look for ID3 tags within an MP3 file, or to look for EXIF data in JPEG files, all on the client side). This is a new API, and replaces the file API that was introduced in Firefox 3.

It is important to note that even before the advent of the W3C File API draft (which only became a Working Draft in November 2009), Firefox 3 and later provide the ability to read files into memory synchronously but that capability should be considered deprecated in favor of the new implementation in Firefox 3.6 of the asynchronous File API. The deprecated API allowed you synchronously access a file:

// After obtaining a handle to a file
// access the file data
var dataURL = file.getAsDataURL();
img.src = dataURL;

While Firefox 3.6 will continue to support code usage of the sort above, it should be considered deprecated since it reads files synchronously on the main thread. For large files, this could result in blocking on the result of the read, which isn’t desirable. Moreover, the file object itself provides a method to read from it, rather than having a separate reader object. These considerations informed the technical direction of the new File API in Firefox 3.6 (and the direction of the specification). The rest of this article is about the newly introduced File API.

Accessing file selections

Firefox 3.6 supports multiple file selections on an input element, and returns all the files selected using the FileList interface. Previous versions of Firefox only supported one selection of a file using the input element. Additionally, the FileList interface is also exposed to the HTML5 Drag and Drop API as a property of the DataTransfer interface. Users can drag and drop multiple files to a drop target within a web page as well.

The following HTML spawns the standard file picker, with which you can select multiple files:

Note that if you don’t use the multiple attribute, you only enable single file selection.

You can work with all the selected files obtained either through the file picker (using the input element) or through the DataTransfer object by iterating through the FileList:

var files = document.getElementById("inputFiles").files;

// or, for a drag event e:
// var dt = e.dataTransfer; var files = dt.files

for (var i = 0; i < files.length; i++) {
  var file = files[i];
  handleFile(file);

}

Properties of files

Once you obtain a reference to an individually selected file from a FileList, you get a File object, which has name, type, and size properties. Continuing with the code snippet above:

function handleFile(file) {
    // RegExp for JPEG mime type
    var imageType = /image/jpeg/;

    // Check if match
    if (!file.type.match(imageType)) {
        return false;
    }
   // Check if the picture exceeds set limit
   if(file.size > maxSize) {
      alert("Choose a smaller photo!");
      return false;
   }
  // Add file name to page
  var picData = document.createTextNode(file.name);
  dataGrid.appendChild(picData);
  return true;
}

The size attribute is the file's size, in bytes. The name attribute is the file's name, without path information. The type attribute is an ASCII-encoded string in lower case representing the media type of the file, expressed as an RFC2046 MIME type. The type attribute in particular is useful in sniffing file type, as in the example above, where the script determines if the file in question is a JPEG file. If Firefox 3.6 cannot determine the file's type, it will return the empty string.

Reading Files

Firefox 3.6 and beyond support the FileReader object to read file data asynchronously into memory, using event callbacks to mark progress. The object is instantiated in the standard way:

var binaryReader = new FileReader();

Event handler attributes are used to work with the result of the file read operation. For very large files, it is possible to watch for progress events as the file is being read into memory (using the onprogress event handler attribute to set the event handler function). This is useful in scenarios where the drives in question may not be local to the hardware, or if the file in question is particularly big.

The FileReader object supports three methods to read files into memory. Each allows programmatic access to the files data in a different format, though in practice only one read method should be called on a given FileReader object:

  • filereader.readAsBinaryString(file); will asynchronously return a binary string with each byte represented by an integer in the range [0..255]. This is useful for binary manipulations of a file's data, for example to look for ID3 tags in an MP3 file, or to look for EXIF data in a JPEG image.
  • filereader.readAsText(file, encoding); will asynchronously return a string in the format solicited by the encoding parameter (for example encoding = "UTF-8"). This is useful for working with a text file, for example to parse an XML file.
  • filereader.readAsDataURL(file); will asynchronously return a Data URL. Firefox 3.6 allows large URLs, and so this feature is particularly useful when a URL could help display media content in a web page, for example for image data, video data, or audio data.

An example helps tie this all together:

if (files.length > 0) {
    if (!handleFile(files[0])) {
        invalid.style.visibility="visible";
        invalid.msg = "Select a JPEG Image";
     }
}

var binaryReader = new FileReader();
binaryReader.onload = function(){
   var exif = findEXIFInJPG(binaryReader.result);
   if (!exif) {
      // ...set up conditions for lack of data
   }
   else {
    // ...write out exif data
   }

binaryReader.onprogress = updateProgress;
binaryReader.onerror = errorHandler;

binaryReader.readAsBinaryString(file);

function updateProgress(evt){
   // use lengthComputable, loaded, and total on ProgressEvent
   if (evt.lengthComputable) {
          var loaded = (evt.loaded / evt.total);
          if (loaded < 1) {
            // update progress meter
            progMeter.style.width = (loaded * 200) + "px";
          }
   }
}

function errorHandler(evt) {
  if(evt.target.error.code == evt.target.error.NOT_FOUND_ERR) {
   alert("File Not Found!");
  }
}

In order to work with binary data, the use of the charCodeAt function exposed on strings will be particularly useful. For instance, an utility of the sort:

function getByteAt(file, idx) {
    return file.charCodeAt(idx);
}

allows extraction of the Unicode value of the character at the given index.

An example of similar code in action in Firefox 3.6, including use of the readAsDataURL method to render an image, as well as binary analysis of a JPEG for EXIF detection (using the readAsBinaryString method), can be found in Paul Rouget's great demo of the File API..

A word on the specification

The existence of a W3C public working draft of the File API holds the promise of other browsers implementing it shortly. Firefox 3.6's implementation is fairly complete, but is missing some of the technology mentioned in the specification. Notably, the urn feature on the File object isn't yet implemented, and neither is the ability to extract byte-ranges of files using the slice method. A synchronous way to read files isn't yet implemented as part of Worker Threads. These features will come in future versions of Firefox.

References

About Arun Ranganathan

More articles by Arun Ranganathan…


38 comments

  1. […] About « W3C FileAPI in Firefox 3.6 […]

    December 9th, 2009 at 15:58

  2. Ivan Enderlin

    Very very very great job guys :-).

    December 9th, 2009 at 16:22

  3. mwilcox

    Awesome guys. I’m looking forward to implementing this in the Dojo FileUploader. One question – can I style it to look like my other form elements and buttons or is that locked down by security (and force us to continue to create hacks to get around it)?

    December 9th, 2009 at 17:12

  4. Aryeh Gregor

    multiple=”true” is invalid. HTML5 defines multiple as a boolean attribute,[1] so you either need to do multiple=”multiple”, or multiple=””, or (in the HTML serialization, not XHTML) just multiple with no = or quotes.[2]

    [1] http://www.whatwg.org/specs/web-apps/current-work/multipage/common-input-element-attributes.html#attr-input-multiple

    [2] http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#boolean-attribute

    December 9th, 2009 at 17:41

  5. rdza

    Incredible job – spec one month, implementation the next, how awesome is that! _And_ async is in the core code so even the callback-phobic are saved from themselves.
    Now we need a w3c fileapi js->as3 shim for IE via flash 10 filereference().

    December 9th, 2009 at 19:46

  6. […] hacks blog has recently put up some great information about the File API along with an excellent demo extracting EXIF data from an […]

    December 9th, 2009 at 23:54

  7. Brian King

    Brilliant. Is this available in Jetpack?

    December 10th, 2009 at 01:46

  8. […] W3C FileAPI in Firefox 3.6 at hacks.mozilla.org FileAPI[API] […]

    December 10th, 2009 at 08:05

  9. thinsoldier

    “(for example, to display a thumbnail preview of an image,…”

    Would it be possible to upload both the original image and the locally generated thumbnail to the server instead of having to process the original image on the server side?

    Lots of my clients try to upload tons of super-big digital photos every day. They hate waiting for their 2mb-8mb files to upload and I hate the noticeable slowdown on my server while it’s generating small, medium, large, and 800×600 versions from the original image.

    December 10th, 2009 at 09:59

  10. Ms2ger

    Please, use <input id=”inputFiles” type=”file” multiple=”” /> or <input id=”inputFiles” type=”file” multiple=”multiple” />. <input id=”inputFiles” type=”file” multiple=”true” /> is invalid. Also, tell me chat will happen to my angle brackets when commenting on your blog.

    December 10th, 2009 at 10:43

  11. […] 3.6 also supports FileAPI. This allows you to do extra processing on the client slide before sending the files to the server. […]

    December 10th, 2009 at 14:20

  12. Arun Ranganathan

    @thinsoldier, if you generate your thumbnail using a DataURL, it’s basically a Base64 dump of the original image. Sure, it’s possible to upload that, but that isn’t the kind of thumbnail you’d ideally want to generate. That being said, there are *probably* ways to manipulate images for lower resolution thumbnails using Canvas. It’s totally worth investigating.

    December 10th, 2009 at 17:04

  13. Arun Ranganathan

    Also, @AryehGregor pointed out that I used the multiple attribute wrong in the original version of this article. You shouldn’t say multiple=”true” but rather simply say multiple=”” or just multiple

    December 10th, 2009 at 17:13

  14. Michael Newton

    Actually, attributes like that should be multiple=”multiple” or, as you say, just multiple. The fact that browsers work with other variations is just a legacy thing.

    http://www.w3.org/TR/REC-html40/intro/sgmltut.html#h-3.3.4.2

    December 10th, 2009 at 21:53

  15. STolsma

    This works awesome but the EXIF data is sometimes not read or not read completely with my own pictures (and also with lots of pictures of my friends).

    Looks like that or the EXIF parser is not totally compliant, my pictures are not EXIF compliant or that there is something else not going well..

    Going to try to find the problem but I’m curious if someone else is experiencing the same..

    December 11th, 2009 at 10:27

  16. schnalle

    that means that the files the user selects are now possibly visible to the server even before the form is sent!?

    i’m actually feeling a bit uneasy about this, but … meh. be careful not to select the wrong file in the file upload dialogs in the future, otherwise its potentially visible to the internets!

    December 14th, 2009 at 06:31

  17. Aryeh Gregor

    @Michael Newton: multiple, multiple=””, and multiple=”multiple” are all valid in HTML5. multiple=”” is newly permitted in HTML5 and was prohibited in HTML 4. The rationale for permitting it is that it’s shorter, and all browsers support it anyway.

    HTML5 also requires browsers to treat multiple=”anything” the same as multiple=”multiple”. So multiple=”true” is guaranteed to work, but it’s invalid.

    Reference:

    http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#boolean-attributes

    Citing the HTML 4 spec in discussion of an HTML5 feature is a little silly. :)

    December 22nd, 2009 at 13:55

  18. webwurst

    Great Stuff!
    It just does _not_ work for me if i have the “Jetpack 0.7” Addon enabled. Needed some time to figure this out ;)

    January 10th, 2010 at 08:38

  19. Marcus Rodriguez

    Excellent stuff, but no mention is made of reading folders/directories. We have several applications where it would be useful to grab whole folders and process them, possible also with sub folders. Is this possible?

    January 14th, 2010 at 04:21

  20. […] Le support de certains formats audio et vidéo « ouverts » pour pousser un peu plus au passage massif vers le HTML5, dont Firefox supporte maintenant toutes les specifications (y compris l’accès aux fichiers). […]

    January 22nd, 2010 at 00:15

  21. demian

    Is it possible to read any file, or just the ones that the user selected? can i guess a system file and steal its contents? no one prevents me from changing the input value…

    February 12th, 2010 at 05:15

    1. schnalle

      it’s not possible to change the input values of file upload elements.

      February 12th, 2010 at 14:00

  22. demian

    right… i didn’t even try that…
    Is it possible to read more than one file with the same FileReader object?

    February 13th, 2010 at 14:29

  23. Greg

    I just released a tool (http://bitdu.mp) that utilizes the File API to do in-browser file encryption (using the jsCrypto library) and then storage of the encrypted data to drop.io. When the file is download, the decryption is done within the browser and then the decrypted data sent back to the client using data URIs.

    February 24th, 2010 at 12:36

  24. […] I used to stimulate discussion. I talked about HTML5 (inclusive of the WebApps APIs, such as the File API and Orientation Events), CSS3’s @font-face property, and discussed the potential this had for […]

    March 11th, 2010 at 03:58

  25. Arnold

    Are there any plans to implement the ‘slice’ function from the FileApi too?

    April 17th, 2010 at 02:19

  26. […] 3.6은 또한 File API를 지원합니다. 이 API는 파일을 서버로 보내기 전에 클라이언트 사이드에서 […]

    May 17th, 2010 at 04:31

  27. […] 원본: http://hacks.mozilla.org/2009/12/w3c-fileapi-in-firefox-3-6/ […]

    June 20th, 2010 at 20:47

  28. Michael Ressler

    I like the direction this is going, especially with the FormData being introduced in Firefox 4. I’m trying to asynchronously send large video files (on the order of 500mb of data) and the FileReader isn’t up to the task.

    When I use the XMLHttpRequest and .send(file), there is a long UI lag (where I can’t interact with Firefox at all) before my server even registers a POST request. On the larger files, the server socket will timeout waiting to read data while Firefox is still busy not letting users interact with it.

    I’ve tried the Sending Binary Data (https://developer.mozilla.org/En/XMLHttpRequest/Using_XMLHttpRequest#Sending_binary_data) example with the nsIFileInputStream code, but the example given doesn’t work and I haven’t yet found the right way of instantiating an nsIFileInputStream and using it. Any input here would be appreciated.

    Is there an expected way of cleanly handling a POST request that contains a file with a few hundred megabytes of data?

    Can anyone point me in the right direction? Thanks for the help!

    August 31st, 2010 at 21:26

    1. Christopher Blizzard

      Yeah, I think that post-Firefox 4 we’ll be adding support for splicing files so you can do partial uploads.

      What happens with the sending binary data example? That should work.

      September 9th, 2010 at 08:52

      1. Michael Ressler

        To clarify, I wasn’t asking about a partial file upload.

        I want to be able to upload the entire contents of a 700mb file through JavaScript without having the user interface lock up on me or have the socket timeout. I assumed that handing the XMLHttpRequest the File object in the .send(file) method would do the right thing behind the scenes (not hang the UI while building the entire request).

        Googling around, it looks like nsIFileInputStream is something that I’ll only have access to if I jump through some fiery signed code hoops. I thought that would have some promise as well, but I don’t see the need for signing my JS in this instance. I’m trying to send the contents of a file that a user browsed to or dropped onto my page. I should be able to send the entire contents of that file back to my server, no matter the size of the file.

        Does Firefox construct the entire HTML POST in the JavaScript/UI thread before it submits the request? Is that why it hangs? Is there any way to have the POST message built outside of the UI thread?

        Thanks for any pointers! It’s much appreciated.

        September 9th, 2010 at 15:29

  29. Michael Ressler

    I don’t have the code ready to test it anymore, but if I remember correctly, the JavaScript error was that I didn’t have permission (or something similar) to access Components.classes. Which meant that I couldn’t instantiate a nsIFileInputStream. Which meant that I was a sad developer and I’ll come right out and confess that I’m currently considering using a flash uploader. I feel so dirty.

    Help!

    September 9th, 2010 at 09:14

  30. Michael Ressler

    The error I got was (as it showed up in Firebug):

    Permission denied for to get property XPCComponents.classes
    var stream = Components.classes[“@mozilla.org/network/file-input-stream;1”]

    September 9th, 2010 at 10:00

    1. Christopher Blizzard

      You don’t have the code to reproduce this anymore? That’s going to make finding this…hard.

      September 9th, 2010 at 10:13

      1. Michael Ressler

        I have the code. It’s not neatly packaged, but it seems like it’s failing on the “Components.classes” lookup based on the error message. Is there some interesting permission code around Components.class? It even fails in Firebug if I just try to inspect Components.class.

        September 9th, 2010 at 10:17

    2. Michael Ressler

      Apparently I can’t use angle brackets in comments here, so I’ll use curly braces instead:

      Permission denied for {http://localhost:8080} to get property XPCComponents.classes
      var stream = Components.classes[“@mozilla.org/network/file-input-stream;1”]

      September 9th, 2010 at 10:15

  31. Miguel Puig

    Thank you very much!.
    Very nive Job!

    June 29th, 2011 at 09:34

  32. […] as a binary array, so you first need to read the content of the file as a binary string, using the File API. Because both Drag and Drop and the input tag allow you to handle multiple files at once, […]

    April 13th, 2012 at 06:57

Comments are closed for this article.