Offline Storage, IndexedDB and the onbeforeunload/unload Problem
October 16th, 2014
Offline storage is one of the best parts about HTML5. No longer do you have to always send client data back to a server where we have to communicate back and forth if we don’t need to. Say we are saving some user preferences for an app, or saving some game data on close/exit. Say you are working on a document and accidentally close or refresh the tab. Offline storage and the unbeforeunload or unload event to the rescue! Well, not quite, as it turns out.
If we are talking about localStorage, there’s hardly an issue here. Since localStorage is a synchronous API, if we save to it in our unbeforeunload event, the browser will wait until it’s done before finally tearing down the page. If you know a bit about localStorage, you will know it also has it’s own problems in terms of how much storage it can hold. Depending on the browser, anywhere from 2.5MB to 10MB. I would say a safe bet is on 5MB. But that’s the issue. Why do I have to guess at how much storage is available? It’s either guess, or do a very intensive save test on the user’s browser to see in each case. Where did 5MB come from? The spec arbitrarily recommends it, yes, that’s a real quote, “a mostly arbitrary limit”. Why not let this be a variable, and ask the user when the app or website tries for more than it’s quota (exactly like some browsers do for IndexedDB)? What if you need to save more data than that? So many questions.
So, our other options? Well Web SQL may have been an option in the past. But there’s lack of browser support and it’s now technically deprecated. IndexedDB is the only real choice for doing what we need to do. Unfortunately, IndexedDB has issues with unbeforeunload/unload events. Since IndexedDB is asynchronous, most (all?) browser vendors have chosen not to let IndexedDB finish saving before the tear down of the page happens. There is currently no known spec or recommendation on this functionality. Sometimes you can save some stuff to it, sometimes not. I say sometimes since this seems to be based on how much data you are saving, if you are refreshing or closing the page, your hardware and browser itself. It’s completely inconsistent as I have found out with some testing. Such inconsistency isn’t something you would expect from interacting with a database and saving data. The MDN even has this to say about this issue:
Second, you should never tie database transactions to unload events. If the unload event is triggered by the browser closing, any transactions created in the unload event handler will never complete. An intuitive approach to maintaining some information across browser sessions is to read it from the database when the browser (or a particular page) is opened, update it as the user interacts with the browser, and then save it to the database when the browser (or page) closes. However, this will not work. The database transactions will be created in the unload event handler, but because they are asynchronous they will be aborted before they can execute.
In fact, there is no way to guarantee that IndexedDB transactions will complete, even with normal browser shutdown.
Yikes, no guarantee about any IndexedDB transactions. So this isn’t only even a onbeforeunload/unload issue in the end. Say you have an app that saves to IndexedDB. The user clicks “Save”, waits a second and then closes the page. Because the user was saving a couple hundred megabytes to their offline storage, the process could never complete before he closed the page. The data is gone. There’s nothing we can do as developers to stop this from happening with the current implementations. Sure, we can ask nicely, like:
window.onbeforeunload = function() { //Put IndexedDB saving here, but will only finish if they decide to stay via dialog choice. return "Some stuff may be unsaved. Are you sure you want to navigate away?"; }
But even that is not cross browser, and we are still not able to actually force a save. All asynchronous processing is stopped while this dialog is up. We can only finish the save if the user decides to stay on the page, and even then, waits for it to finish.
What does this all mean? If you need to save more than (around) 5MB before a user closes, refreshes or exits a page in some way, you can’t. Great gods of the auto save (and spec developers), hear my cries!
Leave a Reply