Saturday, August 22, 2009

sessionStorage cross domain

This is just a quick post about my last HTML5 sessionStorage implementation for "every" browser and this is the summary:

  • Linear Storage Protocol finally consistent as a stand alone general purpose storage (string, file, request)

  • RC4 Stream Cipher key generation without breaking characters (\x00 not in the random range)

  • Cross Domain Support, if both site a.com and site b.com support my implementation, they will not delete or modify each other domain information

  • optimized boot strap for frames and iframes, the storage parsing and reassignment is performed once and not every time which means zero delay when an iframe is injected or more frames uses the same implementation


Finally, few bug fixes and this version is both W3C Draft compatible and cross browser.

Known Supported Browsers



  • Chrome 1 and 2, version 3 or 4 should have native support for Web Storage

  • Internet Explorer 5, 6, and 7, 8 has native support for Web Storage

  • Opera 7, 8, 9, and 10 beta, hopefully 10 final will have native support

  • Safari 3, version 4 has native support for Web Storage

  • Firefox whatever version, since it supports Web Storage since version 1.5

  • Google Android and iPhone, but latest WebKit releases should support Web Storage


This is a mixed list of almost all A-Grade browsers but about Android and iPhone, the total amount of available RAM could be 2 Mb.
It is not suggested to store in any case massive amount of information but so far I have putted every kind of library avoiding server requests for each tab session and without problems.
The sessionStorage aim is, in any case, to remember an arbitrary amount of data for each session and for each tab, surpassing cookies limits without number of keys or amount of data limits.

This project will be part of vice-versa library in order to make vice-versa the cross browser "lingua franca" W3 framework able to bring standards where these are not present yet.

1.4 Special Guests


I would like to say a big thanks to Jonathan Cook (J5 in WebReflection comments) for his suggestions, patience, and ideas exchange ;)

Monday, August 17, 2009

Konami Code as DOM Event

If You do not know what is the Konami Code you are too young, or not geek at all.
In both cases, it does not matter, nobody is perfect, but what we should know is that some truly affectionate developer though to put this code to enable secret user powers, even if the the software is, as example, extremely recent.

So why not? Why can't we all have our secret mode in the most popular platform as the World Wide Web is?

Konami Code Event



var onkonamicode = function(el, fn){
// The Super Code Event by WebReflection - Mit Style
function onkonamicode(e){
konamicode.push((e || event).keyCode || e.charCode);
if(
konamicode.length === 10 &&
konamicode.join(".") === "38.38.40.40.37.39.37.39.66.65"
){
if(el.removeEventListener)
el.removeEventListener("keydown", onkonamicode, false);
else
el.detachEvent("onkeydown", onkonamicode);
fn(e);
};
clearTimeout(i);
i = setTimeout(clear, 1000);
};
function clear(){
konamicode = [];
};
var konamicode = [], i = 0;
if(el.addEventListener)
el.addEventListener("keydown", onkonamicode, false);
else
el.attachEvent("onkeydown", onkonamicode);
};


How To Activate Konami Code


It is that simple:

onkonamicode(document, function(e){
alert("access guaranteed");
});

Now try to perform the magic sequence and that's it, you have extra power!

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.

Saturday, August 15, 2009

Opera Detection Revamped?

I am surfing in a nightmare of problems with IE and Opera right now ... all these problems come from sessionStorage implementation ... I am almost there, but right now just think about this question:

How to know if onload and unload event are supported without firing them?

Apparently, it has never been that simple!

Object.prototype.toString
.call(window.opera) ===
"[object Opera]"
;

Enjoy

P.S. Prototype Framework uses this trick since version 8, I had no idea about that! Good one!

Friday, August 14, 2009

Defeat Internet Explorer - Can You?

While I was creating a stand alone implementation of the LSSP described in my last HTML5 sessionStorage project, I did some test and bench as every Agile developer should do.

Well, what I have discovered, is that apparently Internet Explorer could perform some common task better than any other browser, at least in my good old Intel Centrino 1.6 Ghz.

I have created a benchmark page able to freeze my Firefox 3.5.2, to ask me if I would like to continue an Array.join execution with an average score that points out my Internet Explorer 8 wins with a score under 300 milliseconds against Chrome, Firefox and Safari.

As summary, is my Centrino dead or there's something we all did not consider about JScript engine?

P.S. Opera 10 beta 2 seems to score about 130 milliseconds as average, good stuff!

Thursday, August 13, 2009

HTML5 sessionStorage and RC4 for "every" browser

Update
Version 1.4 has been commented here.

  • fixed iframe problem, now standard behavior

  • a singleton for each context (window, frame)

  • same storage area for each context (main top window)

  • no more disabled flag, typeof sessionStorage !== "undefined" to use it

  • waiting to understand how exactly events are fired but the code is ready to manage them




Yesterday night I twitted about it, this evening I've finally found time to write down the Home Page in Google Code.

You will find everything in the home page, or in the source code, while here there is a first basic demo to try out.

I would like to know your opinion about it, so do not hesitate to write some comment!

P.S. please note that the minified version, once gzipped, is less than 1.5Kb ... enjoy ;)

Thursday, August 06, 2009

PAMPA-J Destiny?

One of my favorite Windows Desktop Application Project, PAMPA-J, is not dead ... it is simply "waiting events", if any.

What Is Going On With Aptana Jaxer


I have no idea! The most exciting news about PAMPA was the J at the end: Jaxer.
Apparently this project is in stand-by: while it has been always present in the Aptana Home Page, it disappeared even from Aptana Cloud Page - apparently, they are not hosting Jaxer anymore ... cool! What should I do then? I know is there a way to go in the Jaxer page, but at the same time precedent signs let me think something truly bad happened to this project. Does anybody have any idea about it?

What Is Going On To PECL For Windows


We all know the "official site" is useless since ages:

The pecl4win build box is temporarily out of service. We're preparing a new build system.

I know PHP 5.3 put in core a lot of PECL libraries such mbstring, iconv, APC, APD, etc etc ... but I wonder when we will be able to add our favorite custom libraries without compiling them for a single machine. I am particularly waiting for runkit and operator, but apparently these two extensions are not going anywhere and PHP 5.3 support is not ready yet. Does anybody have an idea about PECL?

What Is Going On With PHP 5.3


Apparently, the PHP team has to celebrate a lot for their 5.3 release. It is the first time in years they come out with a release that is not overpatched after few days.
As soon as they announced PHP 5.3 I installed it in PAMPA-J without problems (change the php.32 folder, overwrite a couple of apache dedicated files, that's it, some minor simple change in the php.ini and nothing else).
At the same time I was sure in few days they would have patched 5.3 with a 5.3.0.1 but it did not happen. After holidays, and too many days from the event, I am waiting for the 5.3.1 already available as dev distribution.

What Is Going On With MySQL


Good question! After I released PAMPA-J, MySQL has been acquired by Oracle. Right now I have no idea how is the MySQL development status, neither which version should I put in the next PAMPA-J.

What Is Going On With Apache


Nothing so far, but at least I realized for Apache 2, 2.X in PAMPA-J, the suggested PHP version is the one compiled in VS2005 and not 2008 (suggested for IIS). Next PAMPA-J will contain the right version of PHP to guarantee better stability.

What Is Going On With Me


I have to admit I am a bit bored by PHP Programming Language. The language itself is great and fun, but its intrinsic limitations plus missed PECL plus people still writing $email to retrieve a posted mail field, plus articles that are never up to date and suggest PHP4 code, plus people still complaining about short tag while PHP problems are, trust me, much more than that ... plus me working over C# and .Net since months, plus me interested in django, plus ... well, you got the point, PHP is behind as programming language, even behind JavaScript, surely behind Python, Ruby, Java, C#, and others.
I am not talking about "what you can do with PHP look there is Wikipedia and Facebook", I am talking about language malleability, features, etc etc ... just the fact somehow operator overload is not supported slashed me on the floor every time I think about the next revolutionary library able to make everything type hintable. No way ... they are pushing so much to badly clone Java language without considering missed overload, mess with public and static, slow OOP against procedural, double API for everything (OOP/procedural) ... guys, what the hell is becoming PHP language? The Emperor of hybrids languages, so hybrids that one day it will be slower than every ther due to SPL massive usage from everybody trying to surpass PHP limits. Ok, it's malleable cause SPL let us do that stuff, but how much slower is an OO application entirely based on getter and setter for a language that was truly fast when used procedurally? Make a decision, take a direction ... frameworks are castrated right now and there is something wrong we should get rid of ASAP. That's me about PHP.

Sorry for delay, hopefully next PAMPA will arrive soon!

Wednesday, August 05, 2009

isFunction hacked, isCallable solution

After this post, and after some twit exchange with @kangax, and finally after my hack for my function itself, here I am with my definitive solution to know if a passed argument could be called.

var isCallable = (function(toString){
// The Latest WebReflection Proposal
// Recently Updated with "explicit" cast
// thanks to abozhilov for the test case
var s = toString.call(toString),
u = typeof u;
return typeof this.alert === "object" ?
function(f){
return s === toString.call(f) || (!!f && typeof f.toString == u && typeof f.valueOf == u && /^\s*\bfunction\b/.test("" + f));
}:
function(f){
return s === toString.call(f);
}
;
})(Object.prototype.toString);

Speed will be the same with every browser, IE will do some extra check only if passed argument is not a function.
The "native" case could be a performance greedy check and we do not like eval in any case.
The "isFunction" name is not appropriate, cause a function is not a Host Object, as is effectively alert, as example, in Internet Explorer.
isCallable simply gets the best from old proposal, threading edge cases in a specific way for the edge browser, Internet Explorer.
Enjoy!

Monday, August 03, 2009

isFunction or isIENativeFunction ?

One truly common task in JavaScript is to determinate if a specific "reference" is a Function, as we know them, or not. For those guys whose think typeof, instanceof, or Object.prototype.toString are enough, here I am with the proof things are NOT that simple, and these things are really important, business speaking, since these checks are in the core of whatever library we create or use for our Web 2.0 Application, Enterprise or not.

Different Behavior Problem: Internet Explorer


As historical black sheep in the browser panorama for its non standard JavaScript implementation, even for a simple task like the right manifestation of a property or function in core level, Internet Explorer, whatever version, will return false in every case described below:


// alert is a function, that is why I use
// this function to demonstrate
// that for IE alert is not a function
// sounds fun?

alert(typeof alert); // object

alert(alert instanceof Function); // false

alert(Object.prototype.toString.call(alert));
// [object Object]

Function.prototype.toString.call(alert);
// Error: Function expected


NO WAY, Internet Explorer think that every native function is a property with a getter that if invoked, even internally via native prototypes, returns an invokable object rather than its "call".
An invokable object is reproducible in this fake way, and it is basically a function which is an Object ... OMG, did I say function?

Do Not Give Up!


Since there is apparently no way to define if a native function is a function, jQuery library, as example, simplified its own isFunction public static callback.
The reason is ... reasonable:
.isFunction is simpler now, it no longer handles some strange edge cases (in favor of simplicity and performance)

Good stuff, simplicity and performances, plus edge case removal, is probably the right direction. But, what if we would simply like to understand if a function is actually a function?

isIENativeFunction Proposal


As I wrote in the jQuery developer mailing list, this little snippet will most likely solve every problem:

jQuery.isIENativeFunction = function(f){
// WebReflection Proposal

return !!f && // cause we want return true or false

typeof f.toString === "undefined" &&
// cause native functions do not
// contain a toString callback
// as is for every user defined
// function or object, even if deleted
// so next step is a "safe" destructuration
// assumption

/^\s*\bfunction\b/.test(f)
// cause we are looking for a function
// and IE shows them with function
// as first word. Eventually
// there could be a space
// (never happened, it does not hurt anyway)
;
};

How to test if above little monster works as expected?

alert([
jQuery.isIENativeFunction(function(){}), // false
jQuery.isIENativeFunction(alert) // true
]);

In few words with this little overhead, exactly 100 characters once minified

function isIENativeFunction(f){return!!f&&typeof f.toString==="undefined"&&/^\s*\bfunction\b/.test(f)};

we can have under control every kind of check, when and if necessary, thanks to a simple double check that 90% of cases will stop at first call for every browser (I assume we check functions most of the time) and only for IE it will call the second check when the function is a native one. If that will not be a function, well, IE and every other browser will simply return false.

// seriously, I want to know if that
// was a function ...

if($.isFunction(f) || $.isIENativeFunction(f))
f();



Not Only jQuery


What if we would like to have a portable isFunction clever enough to understand every situation only when we need it?

var isFunction = (function(toString, F){
// Another WebReflection Proposal

// test if a function is native
// simply trying to evaluate its
// original Function.prototype.toString call
function test(f, n){
// it should be a function in any case
// before we try to pass it to
// Function.prototype.toString
if(n = isFunction(f, false)){
try{
// no execution
// just an error if it is native
// every browser manifest native
// functions with some weird char
// that cannot be evaluated [native]
this.eval("(" + F.call(f) + ")");

// if eval was fine, function was
// not native
n = false;
}catch(e){};
};
return n;
};

// avoid monkey strings ...
// let the browser decide
// how Object.toString will represent it
// [object Function] 100% of the case
// but IE is always present ...
var s = toString.call(F);
try{
// native function cannot be passed
// to native Function.prototype.toString
// as scope to evaluate ... only IE, sure
F.call(this.alert);
// this function is for every other browser
function isFunction(f, n){
// the good old toString call
// if "n" argument is true
// we check if the function is native
return !n ? s === toString.call(f) : test(f, n);
};
}catch(e){
// IE party!
// even if u was defined outside
// next expression will produce undefined
// the typeof is performed in the var u
var u = typeof u;

function isFunction(f, n){
return !n ? // no native check ?
// god old toString then ...
s === toString.call(f) :
// test if IE trick proposed for jQuery
// works as expected or try
// to understand if that function
// is native via test one
(!!f && typeof f.toString === u && /^\s*\bfunction\b/.test(f)) || test(f, n)
;
};
};
return isFunction;
})(Object.prototype.toString, Function.prototype.toString);

To test above code?

//* Example
// Others | IE
alert([ // --------------
isFunction(function(){}), // true | true
isFunction(Function), // true | true
isFunction(alert), // true | false
"\n", // --------------
isFunction(function(){}, true), // false | false
isFunction(Function, true), // true | true
isFunction(alert, true), // true | true
"\n", // --------------
// all true
isFunction(function(){}) || isFunction(function(){}, true),
isFunction(Function) || isFunction(Function, true),
isFunction(alert) || isFunction(alert, true)
]);
//*/

In few words, every browser but IE will tell us if a function is a function, as is right now with the simplified version of jQuery 1.3 and many other libraries.
On the other hand, we can understand if a function is a native one with every browser.
This operation will cost more and for this reason a true, as second optional argument, is necessary to force other checks.
isFunction(f) || isFunction(f, true) should be always true in every browser if f is a function, and it will be evaluated slowly twice only by IE cause other browsers will not pass the internal isFunction check if f is not a function.

One callback to rull "function investigation cases" all? Dunno what you think, but I like it :D

A JavaScript WorkerLocation

This is a quick post (I am going to write another one) about a pure JavaScript implementation of the WorkerLocation W3C Draft Interface.

Why A WorkerLocation


I've instantly started to interact with Cartagen guys, via Ajaxian, as I think that is one of the most interesting canvas related project I've ever seen so far (I like Bespin idea but I do like more Notepad++ :P )
Since after a first and quick read about the project I proposed Web Workers as possible solution for stressful computations, Jeffrey Warren put on the table the fact that Web Workers are not cross browser yet (Internet Explorer 8 anybody?).
Being a developer whose aim is to solve problems, I started to analyze Web Workers specs in order to re-create a cross browser implementation able to let us develop following new standards and bringing them somehow in those browsers where these standards are not standard yet and gosh knows where they will be (Internet Explorer 9 anybody?). Something Google did before with ex-canvas project: when a browser is behind it cannot block everybody else (it should NOT, at least).
I am not sure how possible/good will be my implementation and it will take time, probably too much to propose it to Cartagen guys but right now I have created a cross browser WorkerLocation which is simple and fast enough to be used in any other project.

The WorkerLocation Code



function WorkerLocation(url){
// WebReflection proposal
var m = /^([^:]+):\/\/([^\/]*)([^\?#]+)(\?([^#]*))?(#(.*))?$/.exec(this.href = "" + url);
if(m === null)
throw new Error("Invalid URL");
this.protocol = m[1] + ":";
this.host = m[2] || "";
this.pathname = m[3] || "";
this.search = m[5] ? m[4] : "";
this.hash = m[7] ? m[6] : "";
m = this.host.split(":");
this.hostname = m[0];
this.port = m[1] || "";
};

You can easily compare the WorkerLocation reliability directly in a loop like this one:

var o = new WorkerLocation(location);
for(var k in o)
alert(k + "\n" + o[k] + "\n" + location[k]);

After several tests, I can say that results are always exactly the same of a native location object.

What's WorkerLocation For


Well, runtime script inclusion a la JSONP or just to load dynamically our library is a common practice and moreover, an URL decomposition attributes could be always useful in different situations, with script sources itself, to load other files, to check a link, etc etc ... enjoy ;)

Sunday, August 02, 2009

PyramiDOM - from Alpha to Beta

As promised, I have commented and improved my last experiment: PyramiDOM, (IE version) now with some new feature:

  • One click from bookmark to add PyramiDOM, another one to remove it

  • In a single session, colors are preserved even if PyramiDOM is added and removed more than once

  • Good browsers with W3C DOM events support and/or a console, will experience a better PyramiDOM thanks to automatic update feature and related dom nodes logged in the console

  • last part of the bookmark is the size of each brick. Right now it is 2 but you can easily change it to 4, 6, 8, whatever multiple of 2 you prefer



The PyramiDOM Source Code


It is really simple and the main logic is behind the Brick constructor and every brick instance.

(function(size){

var // shortcut to attach/detach events
add = document.addEventListener ?
function(node, name, callback){
node.addEventListener(name, callback, false);
}:
function(node, name, callback){
node.attachEvent("on" + name, callback);
}
,
del = document.removeEventListener ?
function(node, name, callback){
node.removeEventListener(name, callback, false);
}:
function(node, name, callback){
node.detachEvent("on" + name, callback);
}
,
// scope shortcut for document.body
body = document.body,
// scope shortcut for parseInt
i = parseInt,
// render timeout varible
t = 0,
// events filter (enable/disable)
exec = true,
// internal shortcuts
node, style
;

// main function, just a loop over each node to create a new Brick stack
function PyramiDOM(/* Brick */ parent, /* HTMLElement */ dom){
for(var child, node, childNodes = dom.childNodes, i = 0, length = childNodes.length; i < length; ++i){
if((node = childNodes[i]).nodeType === 1)
parent.add(PyramiDOM(new Brick(node, parent), node));
};
return parent;
};

// via brick info create a link (for alt/title easy tooltip)
// and append into the dom node
PyramiDOM.render = function render(/* Brick */ parent, /* HTMLElement */ dom){
var a = document.createElement("a"),
style = a.style,
alt = parent.dom.nodeName
;
if(parent.dom.id)
alt += " #" + parent.dom.id;
if(parent.dom.className)
alt += " " + parent.dom.className;

// the id contains this indexOf related brick in the Brick.register stack
a.id = parent.id + "-pyramidom";
// using parseInt will guarantee a quick integer transformation

a.title = a.alt = alt;
style.position = "absolute";
style.margin = style.padding = 0;
style.top = parent.top + "px";
style.left = parent.left + "px";
style.height = parent.height + "px";
style.width = parent.width + "px";
style.background = parent.color;
dom.appendChild(a);
// recursion: for each brick children render a link
for(var children = parent.children, i = 0, length = children.length; i < length; ++i)
render(children[i], dom);
};

// check if the node was already there
// in order to be able to remove PyramiDOM with another click
// in the bookmark menu
PyramiDOM.node = document.getElementById("pyramidom");

// if present (re-clicked) ...
if(node = PyramiDOM.node){
// ... remove node
del(document, "DOMNodeInserted", document.PyramiDOM);
del(document, "DOMNodeRemoved", document.PyramiDOM);
body.removeChild(node);
node.innerHTML = "";
PyramiDOM.node = node = null;
return;
} else {
// create the node
node = PyramiDOM.node = body.appendChild(document.createElement("div"));
style = node.style;
node.id = "pyramidom";
style.top = "0px";
style.left = "0px";
style.position = "absolute";
style.zIndex = 2147483647;
// attach events
add(node, "mousedown", function(e){
var target = (e || event).target;
if(target.nodeName === "A"){
// try to show the related node in the console
try{console.log(Brick.register[i(target.id)].dom)}catch(e){};
}
});
add(node, "mouseover", function(e){
var target = (e || event).target;
if(target.nodeName === "A"){
target = Brick.register[i(target.id)];
if(target.dom.style){
target.background = target.dom.style.background || "";
target.dom.style.background = "yellow";
}
}
});
add(node, "mouseout", function(e){
var target = (e || event).target;
if(target.nodeName === "A"){
target = Brick.register[i(target.id)];
if(target.dom.style)
target.dom.style.background = target.background;
}
});
add(document, "DOMNodeInserted", document.PyramiDOM = function(){
// if modification is NOT PyramiDOM itself
if(exec){
// avoid greedy computations
if(0 < t)
clearTimeout(t);
// be sure last timeout will represent the current DOM
t = setTimeout(function(){
// disable insert/remove events
exec = false;
body.removeChild(node);
PyramiDOM.render(PyramiDOM(new Brick(document), document), node);
body.appendChild(node);
// enable insert/remove events
exec = true;
}, 250);
};
});
add(document, "DOMNodeRemoved", document.PyramiDOM);
};

// each brick is an instance with some computated property
function Brick(dom, parent){
this.dom = dom;
// register this instance (reference via link id)
this.id = Brick.register.push(this) - 1;
// set color via nodeName
this.color = Brick.color(dom.nodeName);
// level means how nested is related dom element
this.level = (this.parent = parent) ? parent.level + 1 : 0;
// calculate the left position of this brick
this.left = this.level * this.width;
// set an empty children stack
this.children = [];
// if this is not the root
if(this.level){
// if the parent has other children
if(parent.children.length){
// get latest child position via top, height, plus 1 pixel as space
var child = parent.children[parent.children.length - 1];
this.top = child.top + child.height + 1;
} else
// this brick is the first for this level
// let's put it a bit more down than its parent
// to obtain the Pyramid effect
this.top = parent.top + (this.width / 2) << 0;
} else
// root, top is 0
this.top = 0;
};

// generate random unique colors based on a generic string, nodeName in this case
// the reason it is cached is to avoid epileptic effect each time the pyramid
// is regenerated (new in this beta version)
Brick.color = document.PyramiDOMColor || (document.PyramiDOMColor = (function(){
function color(hex){return new Array(7 - hex.length).join("0") + hex};
function random(nodeName){
// quickly avoid duplicated
do{var c = (Math.random()*0x1000000)<<0}while(cache[c]);
cache[c] = true;
// cache result to avoid this operation for same nodeName
return hash[nodeName] = "#" + color(c.toString(16));
};
var cache = {}, hash = {};
return function(nodeName){return hash[nodeName] || random(nodeName)};
})());

// every brick object is registered in a global public stack
Brick.register = [];

// every brick has a default size
Brick.prototype.width =
Brick.prototype.height = size;

// every brick has a children stack
Brick.prototype.add = function(child){
this.children.push(child);
this.height += child.height + 1;
};

// create PyramiDOM
document.PyramiDOM();

// size of each brick (2, 4, 6 up to whatever % 2 === 0)
})(2);


Have fun with PyramiDOM :)