Using Iframes and Cross Domain Scripting
@shakedko
IF AN EXPERT SAYS IT CAN'T BE DONE GET ANOTHER EXPERT.
- DAVID BEN-GURION

Using Iframes and Cross Domain Scripting

So I didn't write for a long time, and its about time to do that. I`v few subjects to talk about, but today I will share my thoughts about iframes and cross domain scripting.

Iframe is an old HTML element which lets you load another page inside your current page. People say that iframes are old and useless for us, but I think they wrong.

Why?

Sometimes we want to make things that seems impossible because of our browsers security rules. There are lots of guides and tutorials about cross domain scripting, and seems that some very good implementation in few well known projects:

  1. ICQ On Site
  2. Meebo web toolbar
  3. Wibiya 
  4. Facebook (Application and such)
  5. Google products

You may try and view those products source code, and... if you have enough skill, and I`m sure you do, you will notice that they are using lots of ways to bypass cross domain scripting while trying to be as safe as they can.

So the idea is to allow our customers to use our product while they don't need to understand any programming code.The customers will just have to add a piece of our code to their website and we will be able to control it without any dependency on our customer. Lets just view some examples:

Lets pretend that Facebook and Google are trying to create some kind of chat integration. Facebook finally understood that GTalk is better then Facebook Chat and they want to use GTalk instead. 

  1. Before we start make sure you have some kind of web server on your computer \ remote web server. 
  2. We will have to define our Hosts file and add two domains:

    127.0.0.1 www.google1.com
    127.0.0.1 www.facebook1.com [My implementation doesn\'t really require it, but use it for better understanding)

     if you are using remote web server, don't forget to change 127.0.0.1 to your server's DNS. 

  3. now lets create the following directories and files: 

/frames/
/frames/google.com.html
/frames/facebook.com.html
/frames/default.com.html

My advice is creating two separate Virtual hosts (Examples: Apache, IIS)  so you will feel this implementation as it real. 

After creating local\remote environment we can start use some code. lets start from our "Parent" window, which will be Facebook. 

facebook.com.html is a regular file, which contain the most basic HTML we need for this example:

<!DOCTYPE html>
<html>
    <head>
        <title>Facebook Example</title>
    </head>
    <body>
        <h1>Facebook Page</h1>
        Hello You are viewing Facebook Example page;
    <script>
        /**
        *   Async script load
        *   Same as Google Analytics code.
        */
        window.gtalk = {init:function(object) {
            var scriptElement = document.createElement("script");
            scriptElement.type = "text/javascript";
            scriptElement.setAttribute("async", object.async);
            scriptElement.src = ("https:" == document.location.protocol ? "https://ssl" : "http://www") + object.url;
            var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(scriptElement, s);
        }};
        window.gtalk.init({async:'true',url:".google1.com/frames/init.js"});
    </script>
    </body>
</html>

As you may see, we are loading JavaScript file named "init.js" from google1.com. This file will be our gate to use GTalk.   Note: you don't really need init.js file, and you can use your own implementation, but for my example, I will show it to you: 

//window.gtalk.init was created on our parent page (facebook1.com/facebook.com.html)
window.gtalk.init({async:'false',url:".google1.com/frames/message.js"});
window.gtalk.init({async:'true',url:".google1.com/frames/gtalk.js"});

Same like in Facebook, I`m loading two more files, this is not performance wise and its only for this example.I guess that 'in the real world' I would create some server side script that will merge my files and use Google Closure or any other JavaScript compiler.

Lets get going... 

I`v created one main file for handling postMessage called message.js:

//PostMessage Handler
var authorizedDomain,callback;
//Handle postMessage messages
var messageHandler = function(e){
    if (e.origin == authorizedDomain.replace(/([^:]+:\/\/[^\/]+).*/,"$1")){
        var data = JSON.parse(e.data);
        callback(data);
    };
};
//Make sure we can receive message and attach events
var receiveMessageHandler = function(object){
    authorizedDomain = object.authorizedDomain;
    callback    = object.callback;
    // we have to listen for 'message'
    if (window.addEventListener){
        window.addEventListener('message', messageHandler, false);
    } else {
        window.attachEvent('message',messageHandler);
    };
};
//Handle postMessage, validate browser support.
var postMessageHandler = function(object){
    if (object.win.postMessage){
        try {
            object.win.postMessage(JSON.stringify(object.data),object.origin.replace(/([^:]+:\/\/[^\/]+).*/,"$1"));
        } catch(e){
            console.log(e);
        };
    } else {
        throw "postMessage is not supported by your browser";
    };
};

And another file for GTalk implementation called gtalk.js:

//Configurations
var _configs = {
    base: 'http://www.google1.com'
};
_configs.iframe = _configs.base + '/frames/google.com.html?rand=' + Math.random();
var gtalkDiv = 'gtalkDiv', gtalkIframe = 'gtalkIframe';
//Elements defenitions
var objects = [
    {id: gtalkDiv, type:'div',style:{width:'200px',height:'200px',border:'10px solid black','margin-top':'10px'},innerHTML:'Starting Here'},
    {id: gtalkIframe,scrolling:'no',name:document.location.protocol + '//' + document.domain, type:'iframe',src:_configs.iframe,style:{'margin-top':'10px',width:'210px', height:'210px', display:'block',border:0}}
];
var elements = {};
//Append Elements to Body
var _makeBodyAppend = function(obj){
    var element = document.createElement(obj.type);
    for (var key in obj){
        if (key == 'style'){
            for (var styleName in obj[key]){
                element['style'][styleName] = obj['style'][styleName];
            };
        } else {
            element[key] = obj[key];
        };
    };
    elements[obj.id] = element;
    if (!obj.to){
        document.getElementsByTagName('body')[0].appendChild(element);
    } else {
        document.getElementById(obj.to).appendChild(element);
    };
};
var appendToBody = function(obj){
    if (obj instanceof Array){
        for (var i in obj){
            _makeBodyAppend(obj[i]);
        };
    } else {
        _makeBodyAppend(obj);
    };
};
appendToBody(objects);
//attach your callback when receving message from postMessage
receiveMessageHandler({authorizedDomain:_configs.base, callback:function(data){
    var div = elements[gtalkDiv];
    switch(data.key){
        case 'background':
                div.style.background = data.key;
                div.style.font = '10px arial';
            break;
        case 'gtalk':
                var ndiv = document.createElement('div');
                ndiv.innerHTML = data.value;
                elements[gtalkDiv].appendChild(ndiv);
            break;
        default:
            div.style.background = '#FFFFFF';
            div.style.font = 'verdana 15px';
    }
}});
//postMessage on load
//new - for IE, @see: http://stackoverflow.com/questions/886668/window-onload-is-not-firing-with-ie-8-in-first-shot, search "George"'s answer.
window.onload = new function(){
    appendToBody({type:'div',to:gtalkDiv,id:'loading',innerHTML:'Please wait while loading your GTALK users',style:{font:'12px verdana',color:'#FF00AA'}});
    setTimeout(function(){
        postMessageHandler({win:elements[gtalkIframe].contentWindow,data:{key:'init',value:1},origin:_configs.base});
        elements['loading'].innerHTML = '';
    },4000);
};

If you will go over this code you will find our iframe implementation. You can search for "_configs" in gtalk.js if you didn't find it. So our iframe called google.com.htmland this is the source:

<html>
    <head>
        <style>
            body { margin: 0; padding: 0; }
        </style>
    </head>
    <body>
        <div style="background: green; width: 210px; height: 210px;">
            <h2>GTALK  IFRAME Control</h2>
            <input type="button" id="background" value="background" name="button" /><Br/>
            <input type="button" id="font" value="font" name="button" /><Br/>
        </div>
        <script type="text/javascript" src="http://www.google1.com/frames/message.js"></script>
        <script>
            /*customerDomain will come from window.name when we create the iframe on our customer page we will add <iframe name=document.protocol + '//' + document.domain..*/
            var customerDomain = window.name,
                gtalkHTML = '<ul><li>Shaked Klein Orbach</li><li>User 2</li><li>User 3</li><li>User 4</li><li>User 5</li></ul>';
            //attach events because I`m so lazy
            var setClickEvent = function(obj){
                for (var i in obj){
                    document.getElementById(obj[i].id).onclick = obj[i].func;
                };
            };
            var _basePostMessageObject = {win:window.parent,origin:customerDomain};
            //Merge object because I`m so lazy again
            var mergeObjects = function(obj1,obj2){
                var obj3 = {}
                for(var attr in obj1){
                    obj3[attr] = obj1[attr];
                };
                for (var attr in obj2){
                    obj3[attr] = obj2[attr];
                }
                return obj3;
            };
            setClickEvent([
                {id:'background',func: function(){ postMessageHandler(mergeObjects(_basePostMessageObject,{data:{key:'background', value:'green'}} ));  } },
                {id:'font',func: function(){ postMessageHandler(mergeObjects(_basePostMessageObject,{data:{key:'font', value:'14px blue Verdana'}})); } },
            ]);
            //Handle post message
            receiveMessageHandler({authorizedDomain:customerDomain, callback:function(data){
                switch(data.key){
                    case 'init':
                        postMessageHandler(mergeObjects(_basePostMessageObject,{data:{key:'gtalk', value:gtalkHTML}}));
                    break;
                    default:
                        alert('s');
                };
            }});
        </script>
    </body>
</html>

You may view the demo Here or Download it

Work In Progress 🚧
Discipline