Sunday, July 12, 2009

One of the most common problems in Web 2.0 Development, is the amount of libraries we can possibly include into a single project and conflicts these libraries could have.Some library is truly aggressive. It could change the entire behavior of each other lib present in the page via not-standard or future implementations of some global object prototype, such String, Object itseld, Array, or others.Since this is a well known problem, some library/framework can try to avoid conflicts in different ways. As example, jQuery has a noConflict method which aim is to make development as painless as possible ... but it is not always that simple. One of Elsewhere purpose is to connect a completely different and external scope into the main window, avoiding any sort of conflict a library with its global properties, functions, or prototypes, could have. First example:

new Elsewhere( "scripts/libs/jquery.1.3.2.min.js").execute(function(ready){ // this scope is elsewhere in a perfectly clean environment. // In this case jQuery will be manifested as global "$" function. // To bring jQuery outside, we can use this sandbox parent reference parent.jQuery = function(search, context){ // to make sure we do not search in an "empty" document, // the one part of this sandbox, we pass the context // if not present as parent.document return $(search, context || parent.document); };

// we can call the ready callback ready(parent.jQuery);},[ // one or more arguments to pass into Elsewhere function executionfunction($){

// let's do something on ready event // please note there is no var in front of jQuery // since it has been declared as global jQuery(function(){ // we could use the argument as well // since in this case it is exactly // the same jQuery function $("body").html("Here I Am"); });

}]);

Confused? Please read more ;)

Quick Answer: What Is A Sandbox

Every time we open a new window in our browser, the latter one creates a (almost)clean environment to run, if presents, each JavaScript file. The same happens for each tab, which means that if we define a function Z(){} in tab a, this will not be present in tab b. Finally, same things happens inside frames or iframes as well. Each window, framed, tabbed, or not, will have then its own JavaScript environment and in JS world this is called sandbox.

What Is Elsewhere

Elsewhere is a lightweight, less than 1Kb if minified and gzipped, almost fully cross-browser library (so far compatible with each browser I tested) able to create runtime a sandbox including zero, one, or more scripts. The first showed example, creates a sandbox including jQuery library and bringing its power outside the box.The important thing to understand, is that used function scope will be completely extraneous to the one the function itself has been created ... in few words, that scope will be elsewhere!Thanks to possibility to send arguments when this external function will be executed, and thanks to parent or top reference, it is possible to play between different scopes avoiding conflicts, creating bridges, loading JSONP, and more!

About JSONP, via Elsewhere a response could be more than just a callback. It could be an entire different environment with runtime created bridges. Example:

// output produced by a generic REST service// myservice.server/name/WebReflection

// a function which name is not a problemfunction getReferences(){ return userInfo.name + " has " + userInfo.posts + " posts";};

// a global object which name is not a problemvar userInfo = { name:"WebReflection", site:"http://webreflection.blogspot.com/", posts:12345};

// a bridge for Elsewhere instance// every Elsewhere sandbox has a reference to its isntance// This reference is retrieved via global sandbox window object// and the property @_Elsewherewindow["@_Elsewhere"].getUserProperty = function(name){ return userInfo[name];};

Understanding execute Method: How Elsewhere Works

How can be possible that a function defined inside a scope is totally ignorant about surrounding variables or scopes? It is really simple!Elsewhere is based on Function.prototype.toString de-compilation, creating passed function directly as script inside the sandbox and returning, if status is ready, its value or undefined.The important thing to understand is that if we create a global scope variable, this will be present next call, whenever we decide to perform it.

// do other stuff and ...sb.execute(function(){ return sum(3, 4);}); // will be 7, sum was already defined

alert(typeof sum); // undefined// sum exists only Elsewhere

About the ready status flag, it is instantly true if we do not load external script, while it could be true or false, depending on which browser we are using (in some browser an iframe document with external scripts is loaded sync while with Firefox 3.5 an iframe does not block main page content so it is not sync).

// more than a file ...new Elsewhere([ // order is respected and reliable "jquery.js", "jquery.ui.js"]).execute(function(onjQueryAndUILoaded, initialTime){ // execute is always performed // after everything has been loaded onjQueryAndUILoaded(jQuery, new Date - initialTime);}, [ // we can optionally pass one or more arguments function($, elaspedTime){ // let's understand how much it took alert(elaspedTime); }, new Date]);

Understanding extend Method: Fast Bridge

Every time we execute a function inside an Elsewhere instance, we are de-compiling and injecting it inside another document. This operation is not that expensive but if we need to perform some task in that scope frequently, we could consider to extend an Elsewhere instance via its own method, creating a sort of fast bridge between two or more than two different worlds.

// if an extend key is a global variable // different from undefined // this will simply be the variable from // this sandbox Object:1,

// if the value is not a function // it will simply be saved as is cool:true });

// extend Elsewhere Object.prototypesb.extendObject();

// test itvar o = sb.createObject("test");alert(o.type()); // String

// o is not instanceof this main page Objectalert(o instanceof Object); // false

// but it could be used as if was// an Objectalert(o instanceof sb.Object); // true

alert(sb.cool); // true

As Summary

Elsewhere can open hundreds of possibilities without bringing nothing new, except that browsers are starting to implement different threads for each sandbox (tab, or window) and I expect this library will bring an even more simple way to implement web workers. Right now main points are conflicts resolution, when libraries are able to deal from a sandbox, new JSONP interaction ways, multiple native constructor prototype definitions via one or more Elsewhere instances, just a simple way to load external scripts or ... well, whatever else our imagination could create with such tiny, cross-browser, library. Have fun with sandboxes 8).

I've been playing with this for some time in the past, and I think you should express that this sandbox should not be used for security, since sandboxed scripts have access to the parent window (as you show).

I have made a safe sandbox using this approach that only works on firefox (safe meaning that it wont allow dangerous code to be execute nor traverse the DOM).

//sandbox.sirdarckcat.net/

I also have a contest that uses the sandbox! if you are interested :)

Oh, and jQuery wont work in all cases (but it works most of the time anyway) because you can't import nodes in the current document if they created on other documents (eg. HTMLElement.ownerDocument is different) and are cloned nodes (in that case you should use the importNode function, thing that jQuery doesnt do). This is a non issue most of the times since the operations that jQuery does don't make reference to the document untill they are appended, but a code like this one:

Daniel, Elsewhere purpose is to create one or more sandboxes and execute whatever you need in a clean environment. Elsewhere itself does not change anything about that sandbox, so it is a bit more flexible than your PJ library which is interesting in any case and I did not know it.

sdc, there is nothing secure in any case and Elsewhere purpose as I said is to create an empty environment. Elsewhere purpose is NOT to create anything more secure because security cannot be obtained via a sandbox while a scope with global function or prototypes will not affect and will not be affected from other scripts. That is why I have already implemented a sandbox version of jQuery which could run without problems in a window with Prototype or other libraries.

sdc, the document issue has been solved today via my patch to jQuery, check that post/ticket so I am able to use 100% of jQuery from a sandbox without problems (a simple search via id is a problem if document is not the correct one).

About safety, again, to retrieve the native sandbox window you just need this:var window = function(){return this}();you can use delete to retrieve original objects (not exactly original but a new copy of them).

I have never said Elsewhere make JavaScript more secure, all I was talking about is compatibility between libraries and a space to put whatever we want.

I do not think a sandbox can be considered safe, but if you think so, it is just a matter of time before you'll think different, IMHO :)

As I understand sandbox is something that is meant to be to run unsafe code. I didnt know there was another definition of sandbox.

Oh! and about the sandbox escaping tricks, I know about them, actually I'm very familiar with them.. I could say its my job haha, but as I said before a couple of times, a safe sandbox in firefox is possible.. (acknowledged by gareth heyes, giorgio maone, mario heiderich, and a few other pros :D)

The therm is correct, JavaScript speaking. Gareth asked me to test its sandbox since the beginning and I still tell you it is not safe. Feel free to think what you want but please stop with this flame, OK? Elsewhere is not about security it is about sandboxing, which is a completely different thing, got it? :)

Hope you still monitor this. Do you have a reference which browsers block when a script tag is loaded in an iframe? I currently try to find a way for proper error handling with jsonp when one request goes to /dev/null and is blocking the execution of the rest.