HTML5 Rocks

HTML5 Rocks

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. 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: and filesystem:, respectively. Then, if I wanted to use "test.png" for an img.src, I needed to use its full absolute path: <img src="filesystem:">. 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.

Getting Gmail to handle all mailto: links with registerProtocolHandler

By Paul Irish at

If you use Gmail you may become frustrated when you click a mailto: link by accident and now your desktop client of Outlook or Mail starts up. Thanks to navigator.registerProtocolHandler() (which we've covered here before) you can wire up Gmail as your default mail client for all mailto: links in Chrome and Firefox.

Screenshot of registerProtocolHandler prompt Here's how:
  1. First, open up a Gmail tab. You must do this from the Gmail tab, not your html5rocks one. :)
  2. Open your javascript console (cmd-opt-j on Mac, ctrl-shift-j on Windows) and enter:
  3. navigator.registerProtocolHandler("mailto",
  4. Accept the confirmation from the browser.
  5. Click this mailto: link to test out your new Gmail mailto hookup!

Boom. Enjoy.

If you ever need to removing this setting, you can do that at chrome://settings/handlers in Chrome and Preferences->Applications->mailto in Firefox.

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 

  • 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( 

  • 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.

Defining Scope In IndexedDB Transactions

By Ido Green at

What is IndexedDB? IndexedDB is an evolving web standard for storage of significant amounts of structured data in the browser and for high performance searches on this data using indexes. In other words, IndexedDB is an object store. It is not the same as a relational database, which has tables with collections rows and columns. It is an important and fundamental difference that affects the way that you design and build your applications (more on the - basic concepts).

So what is new? Changes my friends... we have some changes that are going to throw some errors if we don't handle them with simple syntax change.

From version 17 onwards, Chrome will now throw an error if an IndexedDB transaction is not scoped to an object store. Since all reading and writing of data are done within transactions, we need to create a transaction on a database, specify the scope (such as which object stores you want to access) and determine the kind of access (read only or write).

What does it means in code? Well, instead of passing an empty array to our database.transaction:

var transaction = db.transaction([], IDBTransaction.READ_ONLY);

You should scope to a particular object store, or list of object stores:

// all stores (equivalent to what use to be marked as empty array. )
var transaction = db.transaction(db.objectStoreNames, IDBTransaction.READ_ONLY);

// multiple stores:
var transaction = db.transaction(['ObjectStoreName1', 'ObjectStoreName2'],

// single store - these are equivalent
var transaction = db.transaction(['ObjectStoreName'], IDBTransaction.READ_ONLY);
var transaction = db.transaction('ObjectStoreName', IDBTransaction.READ_ONLY);

You can speed up data access by using the right scope and mode in the transaction. Here's a couple of tips: When defining the scope, specify only the object stores you need. This way, you can run multiple transactions with non-overlapping scopes concurrently. Only specify a READ_WRITE transaction mode when necessary. You can concurrently run multiple READ_ONLY transactions with overlapping scopes, but you can have only one READ_WRITE transaction for an object store.

Other sources:

So until next time... keep pushing the web to near territories.

Registering a custom protocol handler

By Eric Bidelman at

Chrome 13 finally includes navigator.registerProtocolHandler. This API allows web apps to register themselves as possible handlers for particular protocols. For example, users could select your application to handle "mailto" links.

Register a protocol scheme like:

    'web+mystuff', '', 'My App');

The first parameter is the protocol. The second is the URL pattern of the application that should handle this scheme. The pattern should include a '%s' as a placeholder for data and it must must be on the same origin as the app attempting to register the protocol. Once the user approves access, you can use this link through your app, other sites, etc.:

<a href="web+mystuff:some+data">Open in "My App"</a>

Clicking that link makes a GET request to Thus, you have to parse q parameter and manually strip out data from the protocol.

It's worth noting that Firefox has had navigator.registerProtocolHandler implemented since FF3. One difference in Chrome's implementation is around custom protocols. Those need to be prefixed with "web+", as seen in the example above. The following protocols do not need a "web+" prefix: "mailto", "mms", "nntp", "rtsp", "webcal".

More information on this API can be found on the MDN article.

navigator.onLine in Chrome Dev channel

By Eric Bidelman at

With the offline APIs in HTML5, there's no excuse not to provide a flawless offline experience for users. One thing that can help this story is the navigator.onLine property; a feature that recently landed in Chrome dev channel. This property returns true or false depending on whether or not the app has network connectivity:

if (navigator.onLine) {
} else {
  console.log('Connection flaky');

A web app can also listen for online and offline events to determine when the connection is available again or when an app goes offline:

window.addEventListener('online', function(e) {
  // Re-sync data with server.
}, false);

window.addEventListener('offline', function(e) {
  // Queue up events for server.
}, false);

I've posted a working demo at and more information on offline events can be found in the MDN.