My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

Sunday, August 16, 2009

W3 HTML5 Storage - What Works, What Does Not

My last sessionStorage is basically ready to implement storage events but apparently I cannot implement them right now. Why not? Doing some test I just discovered a lot of inconsistency for each browser which supports Web Storage in core, so here I am with a little report and some suggestion.

The Correct Way To Set or Get Items


Every single example I've read so far about Web Storage is not respecting standards defined by the official draft, even if related browsers respect the official API.

// bad sessionStorage usage example
if(sessionStorage.myKey !== null){
// not implementable with old browsers
// it could inherit from a modified Storage prototype
// it could be a false positive with native API
} else {
sessionStorage.myKey = "myValue";
// requires getter/setter, improbable with other browsers
// it could overwrite native properties or methods
// sessionStorage.key = "value"; will *break* the object
};

// W3 standard suggested Storage usage
if(sessionStorage.getItem("myKey") !== null){
// implementable with my standard solution
// it *should* *not* interfere with prototypes
// it *should* never be a false positive
} else {
sessionStorage.setItem("myKey", "myValue");
// it does not require get/set alchemy in other browsers
// it *should* *never* create conflicts with native methods
// or properties
};

The problem here is that if we follow W3 we are "safe enough" while if we use the quirks way to access or set properties, we will never know how the browser will react. An error because we tried to overwrite a native property or method? A silent operation that will break the object? A silent operation that will not break anything but that will make the logic inconsistent?

Which Browser Does Not Break Itself


Apparently, the only browser that implements internally a private dictionary, rather then set or get values from the sessionStorage instance itself, is Firefox. Both Internet Explorer 8 and Safari are able to make a sessionStorage, or a localStorage, useless. My implementation? It does not break the object, Firefox behavior!

// IE8 and Safari break theirself

sessionStorage.key; // native method
sessionStorage.key = "value";
// sessionStorage.key is broken, privileged property
// key with value "value" instead

sessionStorage.setItem("setItem", "break it!");
sessionStorage.setItem;
// the string "break it" rather than the API method
// setItem is not usable anymore in the whole page

As we know that we should never extend Object.prototype, how come every browser and every related example here suggests bad practices and/or allows us to easily break these objects?
Via official W3 API and bug fixes, we would like to be absolutely sure that setItem will never break the object itself and that getItem will always be the native API method.

Empty Key Support


OK, this is not a massive issue cause an empty string as key is something too silly to use.
On the other hand, since a Web Storage should support any kind of key as is for a generic object, where object[""] = 123 is normally saved, I wonder why Firefox browser does not save anything and it does not fire any kind of error as well.

// how to perform a ghost operation in Firefox ...
sessionStorage.setItem("", "empty string");
sessionStorage.getItem("") === null; // true !

I would like to have a unified behavior here as well ... don't you agree?

Storage Events - Nobody Compliant


This is the most interesting part of my analysis about Web Storage implementations, nothing is working as expected.
Firefox and Safari do not fire events at all, or at least I have not been able to do it.
Storage Events should be fired in the document, for gosh knows which reason (I rather prefer listeners in storages or directly in the top context, since everything is about the top window context).
Apparently storage events are two: onstorage, and onstoragecommit.
Both events are not described yet in the official draft so these are how Internet Explorer implemented them, and in an inefficient way.

// Internet Explorer is the only one
// able to fire storage events

document.onstorage = function(e){
alert(e); // undefined, global event instead
};

document.attachEvent("onstorage", function(e){
alert(e); // object Event ... hooorray? NO
e.key; // undefined
e.storageArea; // undefined
e.newValue; // undefined
e.oldValue; // undefined
e.target; // undefined
e.type; // storage, nice one
});

Funny enough, a web storage event in IE fires with clientX and clientY properties, extremely useful for a storage operation, isn't it? It does not matter, the problem is that there are no extra arguments and every expected property is not there.
At least, we have an event that fires in every storageSession present in the page, nested pages (i/frames) so it is usable as listener.
Accordingly, first usage of sessionStorage events will require developers skills to be implemented:

document.attachEvent("onstorage", (function(){

// this closure is necessary to
// create a memory variable
var modified = null;

// above variable is useful to manage
// events
return function(){

// unless we do not check each property
// in the storage, there is no way to understand
// what has been changed here

// we check if the sessionStorage is trying
// to tell us that something has been changed
// get("modified") will be null until it is set
// if we have more than a context (frames)
// we would like to perform the task just once
// this is why modified variable is used
if(modified !== sessionStorage.get("modified")){

// we can decide which action we should perform here
// and we should prevent next event execution
switch(modified = sessionStorage.get("modified")){
case "change userName":
// do some query or something else
// with the userName
sessionStorage.get("userName"); /// etc etc ..
break;

case "otherAction":
// ...
}
}
};

// define something
sessionStorage.setItem("userName", "Andrea Giammarchi");
// nothing is performed in the event

// now we want to perform something
sessionStorage.setItem("action", "change userName");

})());


Above example is just simple and not perfect but it is a hint about curent event management at least in Internet Explorer.
As Douglas Crockford did when He discovered JSON, we could enrich our event notifier protocol using JSON rather than plain strings, it is up to us.

Should I Fix Other Browsers ?


At this point I could decide to fix Firefox and Safari plus add events in my implementation for every other browser.
The problem here is that standards are not definitive and IE is alread messing up these standards.
Being sessionStorage and localStorage completely different objects, a type property in the event with value "storage" does not help at all to understand which one out of two has been changed ... or maybe, since onstoragecommit has never been fired in my tests, this event will be associated exclusively to the localStorage only ... who knows!
If I implement this behavior I gonna slow down events assignment due to addEventListener or attachEvent wrappers.
The alternative is to call, if present, the onstorage event for each document part of the same session. Still, it should be in core and a fake event object could just cause more problems so I am kinda stuck with storage events but I am pretty much happy about the rest cause everything seems to work as expected.

As Summary


There is one thing I am concerned about: is this what HTML5 will mean for us? Do we need to test every single new interface to fix or change their behaviors? Isn't HTML5 purpose to make things more standard and less problematic?
As we can see there are already "gotchas" everywhere and for a single, simple, misinterpreted interface as Storage is.
Examples are different and confused, specially the Safari one uses both setItem, getItem, and direct properties access too.
Behaviors are not the same and there is something missing or different for each browser (I'll test Google Chrome ASAP as well).
Well, HTML5 is good, but we all should try to do not mess it up with quick but not-standard or problematic implementations.

No comments: