Creating Google Chrome Extensions – II

Here we are again, let’s continue from where I get stuck on my previous post. I have started creating a google chrome extension, and I was stuck, because the Javascript code being executed on a separated context.

Once I realized which was the problem I started googling around, until I found a script able to execute a given javascript code in page’s context.

Basically, the works by injecting the given javascript code in dom’s page, at the document head section:

<script type="text/javascript">Your code</script></head>

This way it gets executed in the page’s context, instead of the extension’s one. You just have to call injectScript, passing the code to be executed as parameter, and optionally you can even send function arguments, if the given code is a function.

At the begining, I had some problems, because as I said previously, my extension was being injected at the “document_start”. That means, even before the head tag is defined. This was causing a javascript error (undefined head), but I really needed to put the script at the begining, in order to save a copy of window.console object before it was overriden by Magento.

After some attempts, I was able to inject it as soon as the head is defined, just by creating a timer interval, and prepending some lines at the begining of voodooattack’s function. This is how it looks like (I’ll put just the new lines of the injectScript function for the shake of simplicity):

//Wait 1 milisecond because the document's head might be not defined yet, so we cannot append our script
var theDeveloperWorldIsYours = setInterval(function(){ injectScript(restoreConsole)}, 1);
function injectScript(source){
    //Cannot execute this script until head has been defined
    if (document.head === null) {
        return;
    } else {
        clearInterval(theDeveloperWorldIsYours);
    }
...
}

The function execution is prevented until the document.head is defined, where I firstly remove the timer, and that’s it.

After this modifications, I got a quick and dirty solution:

//Function that stores the window.console in a variable to restore if after magento script has remove id
var restoreConsole = function(){
        //Function that checks if console.log has been removed and restores it
        var checkRestore = function(){
                //Once magento removes the console, this method is defined, so he have to restore the old console
                if (typeof window.console.log.argumentNames != 'undefined') {
                        window.console = __consola;
                        if (__retry-- == 0) {
                                console.log("Console restored by TheDeveloperWorldIsYours!");
                                clearInterval(a);
                        }
                }
        }
        __retry = 10;   //Retry several times, as magento is removing console on a loop, and it might take some time
        __consola = window.console;
        a = setInterval(checkRestore, 1);
        //After 1 second, clearInterval
        setTimeout(function(){clearInterval(a);},1000);
}
//On browsing sometimes you have to wait
injectScript(restoreConsole);
//Wait 1 milisecond because the <head> document isn't defined yet, so we cannot append our script
var theDeveloperWorldIsYours  = setInterval(function(){ injectScript(restoreConsole)}, 1);

I was saving console’s object on a global variable, and I then I was waiting until the original window.console is overriden by magento. I noticed that the method argumentNames is undefined on the original code, but not after it’s been replaced, so I used it as flag.

Once the flag is set, I have to replace the object with the original one several times, as I noticed that only one wasn’t enough, because magento’s overriding loop might be still in execution.

I also added a timer removal after one second, in order to prevent infinite timers execution on all other pages.

This code kinda works, but it has several drawbacks, apart from the obvious overuse of timers.
The main problem was that I noticed that it wasn’t working every time. It works once you load a magento page or if you refresh the window. However, once you are browsing around, it seems that the magento code is cached, and therefore it’s being executed right before the extension could inject the code in the document’s head. This broke my code, because I wouldn’t be able to save a original copy of window.console on time.

I was wondering if I could pass window.console object as function argument from the extension’s context, as it wasn’t being overriden by magento, but then I found a solution even better, just by coincidence.

I was having a look at stackoverflow (I’m sorry but I don’t remember exactly which page was), and I saw an alternative way of getting a working window.console object. It sees that you can get it from an iframe, so I just had to dynamically create an iframe, append it to the page and retrieve window.console object from it. This is the final version of the code:

/**
 *  restoreConsole
 *  This code is supposed to be injected into the dom document at the very begining of the page load.
 *  Check for attempts to unset the console.log, and restore it back if needed.
 *  The execution will stop once the page has been loaded.
 *
 *  @author Javier Carrascal <javilumbrales[at]gmail[dot]com>
 *  @return void
 */
var restoreConsole = function() {
    __theDeveloperWorldIsYours = setInterval(function() {
        //Anonymous function that checks if console.log has been removed
        //By default, console.log.name should return "log", otherwhise, something has removed it and we have to restore it
        if (typeof window.console.log.name !== 'log' && document.head !== null) {
            //We can restore the original window.console by creating an iframe and appending it to the document
            var i = document.createElement('iframe');
            i.style.display = 'none';
            //We append it to the head so we don't miss console.log messages sent before the body.
            document.head.appendChild(i);
            window.console = i.contentWindow.console;
            console.log('Console restored by TheDeveloperWorldIsYours!');
            //We are done here
            clearInterval(__theDeveloperWorldIsYours);
        }

    }, 1);
    //Stop the execution of the code after page has been loaded.
    window.onload = function() { clearInterval(__theDeveloperWorldIsYours);};
}

This is IMO a much better approach which actually works well. It might probably be improved, of course, but at least its working fine for my purposes. For instance now I’m able to use Google Analytics Debugger extension on any magento page and check what’s being push at the page load, and I’m finally able to use console.log anywhere and at any time .

If you have any questions/suggestions/improvements, they are more than welcomed.

Special thanks for voodooattack’s blog, because without its script, I wouldn’t have been able to get it working.

Find attached the full code of the extension, just in case you want to give it a try, or just have a look.