if (typeof window.localStorage == 'undefined' || typeof window.sessionStorage == 'undefined') (function () { | |
var Storage = function (type) { | |
function createCookie(name, value, days) { | |
var date, expires; | |
if (days) { | |
date = new Date(); | |
date.setTime(date.getTime()+(days*24*60*60*1000)); | |
expires = "; expires="+date.toGMTString(); | |
} else { | |
expires = ""; | |
} | |
document.cookie = name+"="+value+expires+"; path=/"; | |
} | |
function readCookie(name) { | |
var nameEQ = name + "=", | |
ca = document.cookie.split(';'), | |
i, c; | |
for (i=0; i < ca.length; i++) { | |
c = ca[i]; | |
while (c.charAt(0)==' ') { | |
c = c.substring(1,c.length); | |
} | |
if (c.indexOf(nameEQ) == 0) { | |
return c.substring(nameEQ.length,c.length); | |
} | |
} | |
return null; | |
} | |
function setData(data) { | |
data = JSON.stringify(data); | |
if (type == 'session') { | |
window.name = data; | |
} else { | |
createCookie('localStorage', data, 365); | |
} | |
} | |
function clearData() { | |
if (type == 'session') { | |
window.name = ''; | |
} else { | |
createCookie('localStorage', '', 365); | |
} | |
} | |
function getData() { | |
var data = type == 'session' ? window.name : readCookie('localStorage'); | |
return data ? JSON.parse(data) : {}; | |
} | |
// initialise if there's already data | |
var data = getData(); | |
return { | |
length: 0, | |
clear: function () { | |
data = {}; | |
this.length = 0; | |
clearData(); | |
}, | |
getItem: function (key) { | |
return data[key] === undefined ? null : data[key]; | |
}, | |
key: function (i) { | |
// not perfect, but works | |
var ctr = 0; | |
for (var k in data) { | |
if (ctr == i) return k; | |
else ctr++; | |
} | |
return null; | |
}, | |
removeItem: function (key) { | |
delete data[key]; | |
this.length--; | |
setData(data); | |
}, | |
setItem: function (key, value) { | |
data[key] = value+''; // forces the value to a string | |
this.length++; | |
setData(data); | |
} | |
}; | |
}; | |
if (typeof window.localStorage == 'undefined') window.localStorage = new Storage('local'); | |
if (typeof window.sessionStorage == 'undefined') window.sessionStorage = new Storage('session'); | |
})(); |
This comment has been minimized.
This comment has been minimized.
Alex - all good feedback. I'm fixed point 1+2 - but not 3 (just because it needs a little more thinking - which I don't have this second!). |
This comment has been minimized.
This comment has been minimized.
JasonHanley
commented
Jan 25, 2011
This looks really good, but what about the 'length' property? |
This comment has been minimized.
This comment has been minimized.
alanhogan
commented
Jan 31, 2011
This is essentially just giving cookies the local/sessionStorage API, right? So as JasonHanley asks, aren’t we going to bump into the length limit of cookies? At this point the only difference from just using cookies (besides the API) is that modern browsers are saved from the overhead of sending the data back on each HTTP request, right? Might it be a better idea to instead use a secure server connection to a possibly-cacheable resource to represent local data? Then the polyfill could actually be using a traditional session architecture (cookie contains only a key used for associating the visitor with data stored on the server) instead of a cookie for data, solving the length problem. Other approaches would include using Flash cookies. Thoughts? |
This comment has been minimized.
This comment has been minimized.
@JasonHanley - doh - yes, I've missed that one. I'll update. @alahogan - We're talking about IE7 and IE6 /only/ - because all other browsers support localStorage and sessionStorage. Note that the limit of cookies only affects localStorage, since sessionStorage is being stored in window.name which has around 2mb limit. The idea of deferring this to the server side is completely wrong IMHO, since we'd be moving client side tech to the server - and the whole idea of client storage is that it's, well, stored in the client! Because cookies in IE7 and below are limited to 4,096 bytes, one solution would be to chunk the data to span across multiple cookies. I'd personally need a decent case for doing this and (again, personally) I've not come across this in my client projects as yet. I totally put my hands up and agree it's an issue, but according to Stats Counter, IE6 & IE7 are currently accounting for 15% of the market - that's low enough that it doesn't justify the work: http://gs.statcounter.com/#browser_version-ww-monthly-201001-201101 - of course, if you disagree, fork this script and let me know how you get on :) |
This comment has been minimized.
This comment has been minimized.
akamike
commented
Feb 2, 2011
I'd like to point out one non-IE use case: Fluid.app clears localStorage when the SSB is closed, which makes it useless for storing more persistent data like user preferences for a Userscript. I've been using a modified version of this to get around that. |
This comment has been minimized.
This comment has been minimized.
aFarkas
commented
Feb 6, 2011
@alahogan and remy - You can simply use SharedObject of flash to work around the length and the data-sending isssues of cookies. Like already sayed above, I'm using exactly this script as a base for my polyfill. If flash is available, I'm using ShardObject. The nice thing here is that flash has a default of 100kb storage (~25x more than cookie), but let's the user to allow unrestricted storage space (You can detect, if the storage is exceeded etc.). You can find a simple implementation with external interface @https://github.com/aFarkas/webshim/blob/master/swfs/localStorage/src/Main.as (It's 1.5kb of ActionScript 2.0, works in Flash 7+). @remy - I think the length attribute (similiar to the keys-method and the IDL-access of storage-data) is neither very usefull nor reliable. (If a third-party script is using localStorage and a developer makes assumption on the length of the domStorage, his assumptions will fail. I can't think of a usecase.) Additionally, it will add a performance overhead on initialization. |
This comment has been minimized.
This comment has been minimized.
EmilStenstrom
commented
May 3, 2011
This script does not work when trying to access pages from a file:// URL and instead gives a "Operation is not supported" code: 9 - Exception. This is fixed in YUI with this commit: http://yuilibrary.com/projects/yui3/ticket/2529165 - Maybe add that here too? |
This comment has been minimized.
This comment has been minimized.
No - because WebStorage doesn't work offline because there's no origin to bind to. IIRC this matches the spec. |
This comment has been minimized.
This comment has been minimized.
EmilStenstrom
commented
May 3, 2011
The sessionstorage-exception make it impossible to use localstorage with your snippet. Localstorage does work offline. That's the issue they've fixed. |
This comment has been minimized.
This comment has been minimized.
aFarkas
commented
May 3, 2011
@remy
to:
|
This comment has been minimized.
This comment has been minimized.
EmilStenstrom
commented
May 3, 2011
I've done more research, and localStorage does NOT work in IE and Chrome no matter what you do, only Firefox. Read some idea that all local apps could then access other app's content (Like things work on a regular harddrive, what a mess... ;) ). |
This comment has been minimized.
This comment has been minimized.
ShirtlessKirk
commented
May 6, 2011
That "fix" is simply a silent fail, so the underlying "issue" is not fixed, just ignored. All storage doesn't work on file:/// because file is not an origin (domain). Not sure how Firefox can get around that apart from silent fail or undeclared permissions elevation. There's no way a web browser should be allowed to access contents of a hard drive outside the scope of the application (i.e. the cache). Even then there shouldn't be the remotest possibility of accessing data unrelated to the current domain. |
This comment has been minimized.
This comment has been minimized.
EmilStenstrom
commented
May 6, 2011
@ShirtlessKirk: I think Firefox scopes the file access to only files in the same directory. Firefox 2 allowed access to any file, the sub-folder policy was new in Firefox 3. Other browsers allow no access at all. |
This comment has been minimized.
This comment has been minimized.
nbubna
commented
Jun 20, 2011
@remy Your length should only start at zero if the data is empty to begin with. Also, you should only increment the length in setItem if the key doesn't already exist and only decrement the length in removeItem if the key does already exist. |
This comment has been minimized.
This comment has been minimized.
ricksuggs
commented
Dec 1, 2011
Just for the sake of convention, shouldn't this script use the === operator as opposed to == ? Ex/ if (typeof window.localStorage === 'undefined' |
This comment has been minimized.
This comment has been minimized.
maettig
commented
Jan 4, 2012
Just to let you know, there is a bug in Firefox, known but not fixed since 2009. A simple |
This comment has been minimized.
This comment has been minimized.
webninjataylor
commented
Feb 3, 2012
Just a heads up for anyone running into IE7 "JSON undefined" issue after inserting this polyfill. Grab Crockford's JSON polyfill (https://github.com/douglascrockford/JSON-js/blob/master/json2.js) and insert before this one. Nothing like a polyfill to help a polyfill. Hmmm... PP fixes IE. Something poetic there. |
This comment has been minimized.
This comment has been minimized.
webninjataylor
commented
Feb 3, 2012
Also note you won't be able to use local storage shorthand with this (localStorage.mydata = 'whatever' ... localStorage.mydata). |
This comment has been minimized.
This comment has been minimized.
WillsB3
commented
Feb 8, 2012
Hi Remy - This looks really useful. What licence is it released under? Many thanks! |
This comment has been minimized.
This comment has been minimized.
License: http://rem.mit-license.org |
This comment has been minimized.
This comment has been minimized.
WillsB3
commented
Feb 8, 2012
Thanks :) |
This comment has been minimized.
This comment has been minimized.
brettwejrowski
commented
Feb 29, 2012
Hey Remy, I just came across this and wondered if you thought about implementing this w/o cookies... possibly with globalStorage and userData like this: https://github.com/wojodesign/local-storage-js |
This comment has been minimized.
This comment has been minimized.
@brettwejrowski - I'm inclined to totally agree. |
This comment has been minimized.
This comment has been minimized.
gbakernet
commented
Oct 11, 2012
@remy, With cookies disabled, merely accessing |
This comment has been minimized.
This comment has been minimized.
lebbe
commented
Nov 23, 2012
In safari with private browsing, using local and session storage gives you:
Would be nice with a try/catch at the top, so we also can use this in safari with private browsing. :-) |
This comment has been minimized.
This comment has been minimized.
ghinda
commented
Jul 19, 2013
It seems that Opera Mini does not allow double quotes in cookies, that's why the polyfill is not working on it. I've made a simple fix with two |
This comment has been minimized.
This comment has been minimized.
leachryanb
commented
Jul 23, 2013
Thanks for this. One comment. You've added JSON.parse and JSON.stringify in the getter and setter. I would pull that for a more accurate representation of the API. The native setters and getters require strings, so it might produce unexpected behavior if the polyfill glosses over this. In the setter, I'm okay with leaving it, since the native would not throw an error anyway, it just does a .toString and stores '[object Object]', but in the getter, you'd have to change your logic between the native and the polyfill. So:
Thoughts? |
This comment has been minimized.
This comment has been minimized.
dasilvacontin
commented
Aug 1, 2013
Shouldn't you check if the key was already set before incrementing length? Like:
|
This comment has been minimized.
This comment has been minimized.
vadzappa
commented
Aug 13, 2013
In Privacy mode in Safari window.localStorage exists while it's not operable. so I think function to check if operable needed: and than add check for operable additionally to check for typeof undefined. |
This comment has been minimized.
This comment has been minimized.
contra
commented
Aug 28, 2013
@AirRider3 @vadzappa I put your fixes in https://gist.github.com/Contra/6368485 - I also applied that length fix to remove (it wasn't checking if the key existed before changing the length) |
This comment has been minimized.
This comment has been minimized.
hasdavidc
commented
Jan 7, 2014
@AirRider3 @vadzappa @contra Even with that fix, I'm not sure that it works for private Safari. I think the override of
doesn't do anything because window.localStorage is already implemented as far as private Safari is concerned, and allowing it to be overridden would be a security concern. Please correct me if I'm wrong - I'm still looking for a way to directly use window.localStorage/sessionStorage in private Safari without a data access layer. I tried running Contra's gist without success. |
This comment has been minimized.
This comment has been minimized.
hasdavidc
commented
Jan 20, 2014
I think the only workaround for private browsing Safari requires you to overwrite the localStorage/sessionStorage prototypes, as overriding the object itself has no effect.
Here's the complete gist with more complete localStorage support detection: https://gist.github.com/hasdavidc/8527456 |
This comment has been minimized.
This comment has been minimized.
folktrash
commented
Apr 30, 2014
Best Gist evar? |
This comment has been minimized.
This comment has been minimized.
maerean
commented
May 30, 2014
How to detect if the polyfill is really needed (like on iOS in Private mode where the object exists but it throws exceptions (QuotaExceededError: DOM Exception 22: An attempt was made to add something to storage that exceeded the quota) in all methods) from https://github.com/Modernizr/Modernizr/blob/master/feature-detects/storage/localstorage.js function() {
var mod = 'modernizr';
try {
localStorage.setItem(mod, mod);
localStorage.removeItem(mod);
return true;
} catch(e) {
return false;
}
} |
This comment has been minimized.
This comment has been minimized.
yehosef
commented
Jul 29, 2014
what about wrapping the function definition in the catch to handle support detection as in https://gist.github.com/yehosef/57df0ec37d5096222cf0 You can add more tests for firefox etc. if desired. I'm dealing specifically with the ios private mode browser. |
This comment has been minimized.
This comment has been minimized.
jarrodirwin
commented
Oct 29, 2014
I've combined the ideas from @hasdavidc and @yehosef to account for the non existence of webstorage in older browsers and to accommodate for Safari's private browsing. I've also tweaked the sessionStorage code so that it acts independent of the localStorage and is unique per window per tab to mimic the native behaviour. See https://gist.github.com/jarrodirwin/0ce4c0888336b533b2c4 |
This comment has been minimized.
This comment has been minimized.
AdReVice
commented
Jul 6, 2016
•
hello sir,, |
This comment has been minimized.
aFarkas commentedOct 27, 2010
Hi,
I'm using this nice script as a basis for my webshims lib. I found some bugs/possibilities to improve:
regards
alex