Blowing up LocalStorage (or what happens when you exceed quota)

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?