Sadly enough, we discovered today that our web application was crashing when browsing in private mode on Safari (this happened during a product demo of our CEO 😐). It was a bit unfortunate that we had to discover it like that and that none of us had tested it with Safari in private mode. However, we were not expecting that modern browsers would do such weirdness, even caniuse.com doesn't mention it. But still, we can't assume things like that of course.

It speaks for itself that you don't use things like local storage or session storage for important features but just as little improvements to features. We used it to remember the maptype when a user switches it from road to hybrid or vice versa. So it was not really a major issue if it was not supported by older browsers like IE 7, which we don't support anyway.

However, Safari decided to throw an exception in private mode when trying to access local storage or session storage.

QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to add something to storage that exceeded the quota.

The issue is present on both desktop Safari (Mac OS X) and Mobile Safari (iOS). We didn't wrap it in a try catch and didn't catched the exception which made our application crash. It's our fault. Luckily, the solution is quite simple. You can prevent a complete javascript crash by just wrapping it in a try catch like the code below.

Set item in session storage:

try {
    sessionStorage.setItem('key', 'value');
} catch (e) {
    // handle exception e, provide fallback, ..
}

Get item from session storage:

try {
    sessionStorage.getItem('key');
} catch (e) {
    // handle exception e, provide fallback, ..
}

I made a small RequireJS wrapper class that handles the exceptions but doesn't provide fallback. We could fallback to cookies, but it would be sad to polute every request with cookies for something so stupid. Especially when your application is getting a lot of traffic you have to be careful with what you put in cookies and what not. So that's why I didn't add a fallback, it's not that important and we can't have requests poluted with cookies like this, but of course it's possible to add it in. Below you can find my small RequireJS class, usage is pretty straightforward and doesn't need much explanation I think.

define([], function() {
    'use strict';

        var ClientStorage = {};

        /**
         * Try to set an object for a key in local storage
         * @param {string} k
         * @param {object|string} v
         * @return {bool}
         */
        ClientStorage.setItem = function (k, v) {
            try {
                localStorage.setItem(k, v);
            } catch (e) {
                console.log(e);
                return false;
            }

            return true;
        };

        /**
         * Try to get an object for a key in local storage
         * @param {string} k
         * @return {object|string|null}
         */
        ClientStorage.getItem = function (k) {
            try {
                return localStorage.getItem(k);
            } catch (e) {
                console.log(e);
            }

            return null;
        };

        /**
         * Try to set an object for a key in session storage
         * @param {string} k
         * @param {object|string} v
         * @return {bool}
         */
        ClientStorage.setSessionItem = function (k, v) {
            try {
                sessionStorage.setItem(k, v);
            } catch (e) {
                console.log(e);
                return false;
            }

            return true;
        };

        /**
         * Try to get an object for a key in session storage
         * @param {[type]} k [description]
         * @return {object|string|null}
         */
        ClientStorage.getSessionItem = function (k) {
            try {
                return sessionStorage.getItem(k);
            } catch (e) {
                console.log(e);
            }

            return null;
        };

        return ClientStorage;
    });

Hopefully you've learned something or this has helped you somehow, let me know!

Comments (4)

  • Gravatar capaj

    You should have try/catched your localStorage access in the first place, because localStorage setItem can trow even on browsers that suport it if the domain quota is exceeded.

  • Gravatar VengefulSpaniard

    But try/catchs are expensive, and they specifically said they only use it for a few unimportant values, no browser has such low quota. We use localStorage in our apps to remember volume/mute settings and end up finding the same bug. It's a really shitty policy.

  • Gravatar Max

    Why are the "About", "Work", and "Contact" navigation items on this site missing href values?

  • Gravatar wouterds

    @Max: The site was still WIP, by now those pages should be accessible ?

Post comment

Avatar