HTML5 Rocks

HTML5 Rocks

Drag and drop a folder onto Chrome now available

By Eiji Kitamura at

As web apps evolve, you might have found it handy to let users drag and drop files from the desktop onto the browser to edit, upload, share, etc. But unfortunately, we’ve been unable to drag and drop folders onto web pages. Luckily, beginning with Chrome 21, this issue will be addressed (already available in the Chrome dev channel).

Passing multiple files with drag and drop

Let’s look at a code sample of existing drag and drop.

<div id=”dropzone”></div>

var dropzone = document.getElementById('dropzone');
dropzone.ondrop = function(e) {
  var length = e.dataTransfer.files.length;
  for (var i = 0; i < length; i++) {
    var file = e.dataTransfer.files[i];
    ... // do whatever you want
  }
};

In this example, you can actually drag and drop a file or files from the desktop to your browser, but when you try to pass a folder, notice that it will be either rejected or treated as a File object resulting in a failure.

How to handle dropped folders

Chrome 21 allows you to drop a folder or multiple folders into the browser window. To handle these, you need to change the way you handle dropped objects.

<div id=”dropzone”></div>

var dropzone = document.getElementById('dropzone');
dropzone.ondrop = function(e) {
  var length = e.dataTransfer.items.length;
  for (var i = 0; i < length; i++) {
    var entry = e.dataTransfer.items[i].webkitGetAsEntry();
    if (entry.isFile) {
      ... // do whatever you want
    } else if (entry.isDirectory) {
      ... // do whatever you want
    }
  }
};

Notice that a big difference here is that you can treat a dropped object as Entry (FileEntry or DirectoryEntry) by using new function called getAsEntry (webkitGetAsEntry).
After obtaining access to the Entry object, you can use standard file handling methods that were introduced in the FileSystem API specification. For example, this example shows how you can detect if a dropped object is a file or a directory by examining the .isFile (or the .isDirectory) field.

For further information regarding the FileSystem API, read Exploring the FileSystem APIs, regarding new drag and drop capability, read this document. Of course, these features are open standards or are waiting to become one soon.

Taking an Entire Page Offline using the HTML5 FileSystem API

By Eric Bidelman at

Let's face it, AppCache is annoying and has problems [1, 2, 3]. One big limitation is the fact that it's impossible to dynamically cache assets on demand. Essentially, this makes it an all or nothing thing when it comes to taking an app offline. Either everything in the manifest is cached up front, or nothing is cached.

The HTML5 FileSystem API becomes an attractive solution for AppCache's shortcomings. One can programmatically store files and folder hierarchies in the local (sandboxed) filesystem and subsequently add/update/remove individual resources as necessary. My colleague, Boris Smus, even wrote a nice library to manage this type of offline caching in the context of games. The same idea can be extrapolated to work with any type of web app.

crbug.com/89271 is an important fix for the FileSystem API which makes relative filesystem: URL paths work like a charm.

Let's say for example, I've saved index.html in the filesystem's root folder (fs.root), created an img folder, and saved "test.png" in it. The filesystem: URL for those two files would be filesystem:http://example.com/temporary/index.html and filesystem:http://example.com/temporary/img/test.png, respectively. Then, if I wanted to use "test.png" for an img.src, I needed to use its full absolute path: <img src="filesystem:http://example.com/temporary/img/test.png">. That meant rewriting all of the relative urls in index.html to point to corresponding file's filesystem: URL. Not cool! Now, with this bug fix, I can keep the relative path to the file (<img src="img/test.png">) as they will resolve correctly to a filesystem origin.

This feature makes it trivial to pull down a page and save all of its resources offline, while still preserving the exact same folder structure as the online version.

Quota Management API : Fast Facts

By Eiji Kitamura at

There’s various offline related features introduced to modern browsers through HTML5. While offline is convenient, its concept of quota has been left untouched for a long time. The latest version of Chrome browser has the first concept and its implementation of Quota Management API . It handles quota for AppCache, IndexedDB, WebSQL and File System API. Here’s a list of things you should keep in mind when working with Quota Management API in the latest Chrome.

(The specific numbers and details noted below are not a part of HTML5 but the facts in the current Chrome implementation. The API and Quota management in Chrome is still evolving and the details may change over the time.)

  • On HTML5 storage, there’s a notion of TEMPORARY storage and PERSISTENT storage.
    • TEMPORARY storage can be used without requesting quota, but may be deleted at the browser’s discretion.
    • PERSISTENT storage is never deleted without the user’s instruction, but usually requires up-front quota request to use.
  • For TEMPORARY storage, it is shared between all applications and websites run in the browser.
    • TEMPORARY storage has a default quota of 50% of available disk as a shared pool. (50GB => 25GB) (Not restricted to 1GB anymore)
      [To be more specific, TEMP quota is calucalated by (remaining disk space + TEMP storage space) * 50%. Therefore if apps are using 25GB TEMP storage in total and the current remaining disk space is 25GB that’s already full.]
    • Each application has a limitation to have 20% of the available TEMPORARY storage pool (i.e. 20% of 50% of available disk). (Not restricted to 5Mb anymore)
    • When TEMPORARY storage quota is exceeded, all the data (incl. AppCache, IndexedDB, WebSQL, File System API) stored for oldest used origin gets deleted .
      [Note: since each app can only use up to 20% of the pool, this won’t happen very frequently unless the user has more than 5 active offline apps.]
    • If app tries to make a modification which will result in exceeding TEMPORARY storage quota (20% of pool), an error will be thrown.
    • Each application can query how much data is stored or how much more space is available for the app by calling queryUsageAndQuota() method of Quota API.
    • Requesting more quota against TEMPORARY storage doesn’t do anything.
    • Requesting quota for TEMPORARY storage using webkitRequestFileSystem() doesn’t actually allocate / change quota.

  • webkitStorageInfo.queryUsageAndQuota( 
        webkitStorageInfo.TEMPORARY,   // or PERSISTENT 
        usageCallback, 
        errorCallback);

  • For PERSISTENT storage, its default quota is 0 and it needs to be explicitly requested per application using requestQuota() of Quota API.
    • The allocated space can be used only by File System API (for PERSISTENT type filesystem) and there’s no such thing like PERSISTENT storage on IndexedDB, WebSQL DB or AppCache (yet).

  • webkitStorageInfo.requestQuota( 
        webkitStorageInfo.PERSISTENT
        newQuotaInBytes,
        quotaCallback,
        errorCallback);

  • unlimitedStorage on manifest.json of Chrome Web App has been brought as a temporary solution for apps to work without Quota API. So, there’s no guarantee that Chrome will support this feature forever.

Debugging the Filesystem API

By Eric Bidelman at

The HTML5 Filesystem is a powerful API. With power, comes complexity. With complexity, comes more debugging frustrations. It's an unfortunate fact that the Chrome DevTools currently do not have support for the Filesystem API. It makes debugging it more difficult than it should be. By difficult, I mean being required to write code to list/remove files in the filesystem.

During my endeavors with the Filesystem API, I picked up a couple of tips along the way that I thought I'd pass along. Each tip has its own set of limitations, but using a combination of these will get you 90% of the way. Here are the top 5:

  1. Make sure you aren't running from file://. This is a sneaky one that a lot of people get bit by. Chrome imposes major security restrictions on file://. Many of the advanced file APIs (BlobBuilder, FileReader, Filesystem API,...) throw errors or fail silently if you run the app locally from file://. If you don't have a web server handy, Chrome can be started with the --allow-file-access-from-files flag to bypass this security restriction. Only use this flag for testing purposes.

  2. The dreaded SECURITY_ERR or QUOTA_EXCEEDED_ERR. This usually happens when attempting to write data but you're under the influence of #1. If that's not the case, than it's likely that you don't have quota. There are two types of quota that the filesystem can be opened with, TEMPORARY or PERSISTENT. If you're using the latter, the user needs to explicitly grant persistent storage to your app. See this post on how to do that.

  3. filesystem: URL FTW. It's handy to open the filesystem: URL for the root DirectoryEntry of your app's origin. What does that mean? For example, if your app lives on www.example.com, open filesystem:http://www.example.com/temporary/ in a new tab. Chrome will show a read-only list of the files/folders stored your app origin. For more info on filesystem: URLs, see http://www.html5rocks.com/en/tutorials/file/filesystem/#toc-filesystemurls.

  4. chrome://settings/cookies is your friend. This page allows you to nuke the data stored for an origin. That includes database storage, AppCache, cookies, localStorage, and stuff in the FileSystem API. Be forewarned though, it's an all or nothing thing. You can't remove just one file or pieces of data.

  5. Don't forget about error callbacks. The Filesystem API lives in an asynchronous world (unless you're using the sync version in Workers). Always use error callback in your API calls. They're optional, but you'll save yourself a lot of grief later on when things blow up.

Saving generated files on the client-side

By Eli Grey at

Have you ever wanted to add a Save as… button to a webapp? Whether you're making an advanced WebGL-powered CAD webapp and want to save 3D object files or you just want to save plain text files in a simple Markdown text editor, saving files in the browser has always been a tricky business. Usually when you want to save a file generated with JavaScript, you have to send the data to your server and then return the data right back with a Content-disposition: attachment header. This is less than ideal for webapps that need to work offline. The W3C File API includes a FileSaver interface, which makes saving generated data as easy as saveAs(data, filename), though unfortunately it will eventually be removed from the spec. I have written a JavaScript library called FileSaver.js, which implements FileSaver in all modern browsers. Now that it's possible to generate any type of file you want right in the browser, document editors can have an instant save button that doesn't rely on an online connection. When paired with the standard HTML5 canvas.toBlob() method, FileSaver.js lets you save canvases instantly and give them filenames, which is very useful for HTML5 image editing webapps. For browsers that don't yet support canvas.toBlob(), Devin Samarin and I wrote canvas-toBlob.js. Saving a canvas is as simple as running the following code:

canvas.toBlob(function(blob) {
  saveAs(blob, filename);
});

I have created a demo of FileSaver.js in action that demonstrates saving a canvas doodle, plain text, and rich text. Please note that saving with custom filenames is only supported in browsers that either natively support FileSaver or browsers like Google Chrome 14 dev and Google Chrome Canary, that support <a>.download or web filesystems via LocalFileSystem.

View FileSaver.js demo

How to construct files for saving

First off, you want to instantiate a BlobBuilder. The BlobBuilder API isn't supported in all current browsers, so I made BlobBuilder.js which implements it. The following example illustrates how to save an XHTML document with saveAs().

var bb = new BlobBuilder();
bb.append((new XMLSerializer).serializeToString(document));
var blob = bb.getBlob("application/xhtml+xml;charset=" + document.characterSet);
saveAs(blob, "document.xhtml");

Not saving textual data? You can append binary Blobs and ArrayBuffers to a BlobBuilder too! The following is an example of setting generating some binary data and saving it.

var bb = new BlobBuilder();
var buffer = new ArrayBuffer(8); // allocates 8 bytes
var data = new DataView(buffer);

// You can write (u)int8/16/32s and float32/64s to dataviews
data.setUint8 (0, 0x01);
data.setUint16(1, 0x2345);
data.setUint32(3, 0x6789ABCD);
data.setUint8 (7, 0xEF);

bb.append(buffer);
var blob = bb.getBlob("example/binary");
saveAs(blob, "data.dat");
// The contents of data.dat are <01 23 45 67 89 AB CD EF>

If you're generating large files, you can implement an abort button that aborts the FileSaver.

var filesaver = new FileSaver(blob, "video.webm");
abort_button.addEventListener("click", function() {
  filesaver.abort();
}, false);

Seek into local files with the File System API

By Seth Ladd at

If you have a File object (say, one stored using the FileSystem API), it's possible to seek into it and read chunks without reading the entire file into memory:

var url = "filesystem:http://example.com/temporary/myfile.zip";

window.webkitResolveLocalFileSystemURL(url, function(fileEntry) {
  fileEntry.file(function(file) {
    var reader = new FileReader();

    reader.onload = function(e) {
      var ab = e.target.result; // arrayBuffer containing bytes 0-10 of file.
      var uInt8Arr = new Uint8Array(ab);
      ...
    };

    var blob = file.webkitSlice(0, 10, "application/zip");  // mimetype is optional
    reader.readAsArrayBuffer(blob);
  }, errorHandler);
}, errorHandler);