Based on some discussion earlier today on Twitter, I wanted to take a quick look at what happens when you exceed the quota limit in a browser's LocalStorage system. I knew an error would be thrown, but I was curious about the type, message, etc. I built a quick test and threw some browsers at it. This probably isn't the most scientific test, but here's what I found.

First, for my test I wanted a quick way to hit the "typical" limit of 5 megs per domain. To do that, I found an image of around one meg in size and then wrote code that would grab the binary bits, convert to base64, and then store it. Here's my test script.

/*
credit:
http://stackoverflow.com/a/18650249/52160

*/
function urlTo64(u, cb) {
  var xhr = new XMLHttpRequest();
  xhr.open('GET', imgurl, true);
  xhr.responseType = 'blob';

  xhr.onload = function(e) {
    if (this.status == 200) {
      // get binary data as a response
      var blob = this.response;
      var reader = new FileReader();
      reader.readAsDataURL(blob);
      reader.onloadend = function() {
        base64data = reader.result;
        cb(base64data);
      }
    }
  };
  xhr.send();

}

$(document).ready(function() {

  console.log("Lets add some stuff");
  imgurl = "xmax2010.jpg";
  urlTo64(imgurl, function(s) {
    console.log("urlTo64 result length",s.length);
    for(var i=0;i<500;i++) {
      if(i > 0 && i % 100 === 0) console.log("A set done");
      try {
        localStorage["bigimage"+i] = s;
      } catch(e) {
        console.log(e.toString());
        console.dir(e);
        break;
      }
    }
    console.log("Done setting big images");
  });
});

I'm using jQuery here, but I don't really need to. I loaded it up when I began but I didn't end up needing it. This isn't really important but I'm just trying to defend my somewhat shoddy test script. ;) The idea is simple - use XHR to fetch the bits of an image (hard coded to one on my test server), convert it to a DataURL and read in the base64 data. You can see that in the urlTo64 function. I then put this in a loop and tried to store the result. You'll notice my loop goes from 1 to 500. Originally it just went to 5. I'll explain the 500 when we get to Internet Explorer.

So - that's it. Read the binary, convert, store, and on error, print it out and stop working. Ok, so let's look at how different browsers handle this.

Chrome 41 (OSX)

Throws an exception with the name QuotaExceedError. The code is 22. The message is nice as it tells you what key it was trying to set:

chromedesktop

Firefox 37 (OSX)

Throws an exception, but with a completely different name/code. The name is NS_ERROR_DOM_QUOTA_REACHED and the code is 1014.

firefox

Safari 8.0.5

Throws the same exception as Chrome (name and code anyway):

safari

Internet Explorer 11 (Windows 10)

So, IE really threw me for a loop. When I ran my code (again, I had started with a loop of 5), it didn't throw an error. So I thought, ok, it has a bigger cache. So I added a 0. And then another 0. And another. I got it up to 5K calls and it still worked. That seemed... wrong. I did some Googling and turns out that IE supports a non-standard remainingSpace property. (Non-standard but a good idea imo. Client-storage does not help developers at all in terms of managing space.) When I inspected that value, it never seemed to change no matter how many additional 0s I added. I could inspect localStorage in the console and see all the values just fine.

ie11

Then on a whim I tried something. I killed IE, re-opened it, and discovered that localStorage only had 2-3 of my images stored. From what I can tell, IE11 stopped storing items, but never threw an error! Which is really, really bad. Even worse, if you try to read the value, it reads it just fine, but on restart, it is gone. I'm not exactly sure what to think about that, outside of the fact that "silent fail" is the worst thing to happen to development since starting arrays at 0.

I tried an interesting little test. I checked remainingSpace before and after a set, and when the set fails, you can clearly see the space does not change. In theory, you could use this (on IE11 anyway) to confirm a proper save.

ie112

As an aside, Jonathan Sampson tried the latest Spartan build with my code and saw the same.

iOS Safari and PhoneGap/Cordova

It throws the exact same error as desktop Safari.

Android Chrome and PhoneGap/Cordova

It throws the exact same error as desktop Chrome.

Takeaway

So, yeah, what's the practical takeaway from this?

  • Pretty much all client-side storage options suck really bad at quota. I love client-side storage (and have presented on it many times) but this is the biggest area of concern.
  • Try to keep track of how you use client-side storage. So for example, if you saving the last 10 searches so you can display them to the user, know that and note it somewhere so that when you make use of client-side storage in the future ("Hey, can we cache some fonts?") you can check and see what you've stored already and see if you'll be hitting the limit possibly.
  • Wrap everything in a try/catch? Um - maybe. ;) The "Super Strict Lets Do Everything Perfect" side of me says yes, but the "Practical I Live in the Real World" says that would probably be overkill. Again, going to my last point, as long as you keep track of what you're doing then I think you will be ok. Obviously I think folks will disagree with me here. Let me have it in the comments.