Tuesday, September 22, 2009

Omniture and document.write()

We had been having serious trouble deploying Omniture code on some of our reservation pages.

For many pages, everything worked fine, the pages loaded as normal, the Omniture code executed, everyone was happy. But for some pages, we would see the page load quickly, then flash blank (white) and hang.

The problem was occurring in both Firefox and Internet Explorer, in different pages with similar customizations... in short, we had no clue.

The only way to really debug the problem was to step through it using a debugger. Luckily, the problem was occurring on a certain page in Firefox, which meant that we could use our high-power, trusty Firebug debugger.

Unfortunately for us the debuggers, the Omniture code is both minimized and obfuscated. Not only that, but much of the code is wrapped into long strings and then later eval()'d. In a nutshell, this was some very tricky code to follow.

When stepping through it in firebug, you can't go "line-by-line" like with normal code, because the code is either all in one line, or it's actually being eval()'d from a string. And even then, all the variable names are meaningless jibberish.

The way we ended up figuring it out was to use a About.com's JavaScript Formatter. While this doesn't de-obfuscate the code, it at least made it readable. What I did was to copy/paste the source for the code into notepad, then delete each instance of " + ", leaving us with the actual code, which I pasted into the textarea on the about.com page.

Then, back in firebug, I could step through the code and even though I couldn't see the line-by-line, I was able to determine which function I was in based on the list of local variables in the Watch window:

I just grep'd for those variables within the page of formatted code in the about.com window until I found a match.

Eventually, after a lot of back and forth, I was able to track the offending function which made the screen turn blank down to this:

s.ca = function() {
var s = this, imn = 's_i_' + s.fun;
if(s.d.images && s.apv >= 3 &&!s.isopera && (s.ns6 <>= 6.1)) {
s.ios = 1;
if(!s.d.images[imn] && (!s.isns || (s.apv <>= 5))) {
s.d.write('<im' + 'g name=' + imn + ' height=1 width=1 border=0 alt=>');
if(!s.d.images[imn])s.ios = 0}

Firebug told me that "s.d" had been assigned to "document" earlier in the code, so basically this line:

s.d.write('<im' + 'g name=' + imn + ' height=1 width=1 border=0 alt=>');

...was really

document.write('<img name=' + imn + ' height=1 width=1 border=0 alt=>');

At that point, a little googling on the vulnerability of document.write() usage led me a really useful page (again from about.com) aptly titled document.write.

The key takeaway from this page was this line:

Any document.write statement that runs after the page finishes loading will create a new page and overwrite all of the content of the current page.

Eureka! That was exactly the problem... the page was already loaded when this line of code was being called, so it was blowing away the whole page.

... and the pages that *weren't* getting blown away had heavier html code that was not getting loaded in time. A good old fashioned race condition!

To solve the problem, we realized that when we were adding the Omniture script to the page, we were doing so as follows:

var scriptElem = document.createElement("script");
scriptElem.src = 'https://www.reztrip.com/Rezimages/extra/4259331/s_code_remote.js';
scriptElem.language = "JavaScript";

this was obviously not loading the script fast enough... it was waiting until the very end of the page.

What we really wanted to do was something more like:


..but we'd come across problems with directly adding the script tag that way.

Finally, to solve the problem once and for all, we found some tips on a post in a Webmaster World Forum entitled document.write("<SCR" + "IPT>"), why?.

The idea they suggest is to break up the actual "<script>" tag, for this reason:

Depending on the browser, the amount of other preceding javascript, and how well-formed the overall code is, this is done to prevent the parser from interpreting the <script> and </script> tags as executeable code rather than as a string to be written.

...we did that and it worked like a charm! Problem solved.

document.write("<scri" + "pt>https://www.reztrip.com/Rezimages/extra/4259331/s_code_remote.js</script>");