contact = {

email: rob@ravelrumba.com,

twitter: @ravelrumba }

Cleaning Up Ad Server Scripts

If you work on sites with ads you know that few things can muck up your efforts to improve a site’s performance like an ad server–3rd party ad tags, iFrames, lots of document.write, and almost all of it out of your control.

In his P3PC series, Steve Souders is currently analyzing the performance of popular 3rd party content, including some ad server implementations. In the same spirit I’ve taken a critical eye to the ad implementation code for 24/7 Real Media’s Open AdStream (OAS) ad server, an ad server I’ve been working with for a few years. It does some things very well but like a lot of ad delivery engines, the code can get a little ugly.

Of the several implementation options that OAS offers, the most sophisticated, the one recommended for Rich Media delivery, and the one we’re going to look at is the “MJX” method.

The nice thing about this method is that all of the ads on the page are retrieved from the server in a single request. Unfortunately, the ads are written to the page with document.write, which blocks other page components and limits our options for asynchronous loading.

Original Implementation Snippet

Here is the MJX implementation code as specified in the OAS documentation. The majority of the code sets up the call to the ad server; the last two lines are the local calls for the individual ads.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!-- OAS Set-up -->
<SCRIPT LANGUAGE=JavaScript>
<!--
OAS_url = 'http://mfr.247realmedia.com/RealMedia/ads/',
OAS_sitepage = 'www.247realmedia.com/site-demo-formats/1',
OAS_listpos = 'x25,x30';
OAS_query = '';
OAS_target = '_top';
OAS_version = 10;
OAS_rn = '001234567890'; OAS_rns = '1234567890';
OAS_rn = new String (Math.random()); OAS_rns = OAS_rn.substring (2, 11);
function OAS_NORMAL(pos) {
  document.write('<A HREF="' + OAS_url + 'click_nx.ads/' + OAS_sitepage + '/1' + OAS_rns + '@' + OAS_listpos + '!' + pos + '?' + OAS_query + '" TARGET=' + OAS_target + '>');
  document.write('<IMG SRC="' + OAS_url + 'adstream_nx.ads/' + OAS_sitepage + '/1' + OAS_rns + '@' + OAS_listpos + '!' + pos + '?' + OAS_query + '" BORDER=0></A>');
}
//-->
</SCRIPT>
 
<SCRIPT LANGUAGE=JavaScript1.1>
<!--
OAS_version = 11;
if (navigator.userAgent.indexOf('Mozilla/3') != -1 || navigator.userAgent.indexOf('Mozilla/4.0 WebTV') != -1)
  OAS_version = 10;
if (OAS_version >= 11)
  document.write('<SCR' + 'IPT LANGUAGE=JavaScript1.1 SRC="' + OAS_url + 'adstream_mjx.ads/' + OAS_sitepage + '/1' + OAS_rns + '@' + OAS_listpos + '?' + OAS_query + '"><\/SCRIPT>');//-->
</SCRIPT>
 
<SCRIPT LANGUAGE=JavaScript>
<!--
document.write('');
function OAS_AD(pos) {
  if (OAS_version >= 11)
    OAS_RICH(pos);
  else
    OAS_NORMAL(pos);
}
//-->
</SCRIPT>
 
<!-- Ad Calls -->
<SCRIPT LANGUAGE=JavaScript><!--OAS_AD('x25');//--></SCRIPT>
<SCRIPT LANGUAGE=JavaScript><!--OAS_AD('x30');//--></SCRIPT>

As you can see there’s a lot of room for improvement here. First let’s take care of the easy stuff.

  1. It’s no longer 1996, so we can get rid of the HTML comments inside the scripts.
  2. For the same reason we can also remove the language attributes on the script elements.

Now let’s look at the OAS_version variable, which is the rascal causing most of the unnecessary code. First, the variable is initialized with a value of 10. Then (line 19), as a means of detecting JavaScript 1.1 support, a JavaScript 1.1 script block is used to set the value to 11. Here’s where it really gets odd.

Next, the script browser-sniffs for Netscape Navigator 3 and what I think might be IE 4 (!). Just to remind you, these browsers were released in 1996 and 1997, respectively.

If the browser is not Netscape Navigator 3 or IE4, and if it understands Javascript 1.1 (also released in 1996), then the call to the ad server is made (line 25).

The next script block defines the function called in the ad unit calls. If OAS_version = 11, the ads returned in the aforementioned request are loaded. Otherwise the previously defined OAS_NORMAL function is called, loading static link-and-image ads.

Somewhat amazingly, the majority of the code is focused on providing compatibility for browsers that have been extinct for a very, very long time. It’s unfortunate, in 2010, to see code like this in official documentation for a major ad server. The good news: unless you have a lot of users accessing your site via a time machine, this is all code that can safely be removed.

Revised Snippet (In which we reduce 35 lines of code to 7)
<!-- OAS Set-up -->
<script type="text/javascript">
    var OAS_url = 'http://mfr.247realmedia.com/RealMedia/ads/',
        OAS_sitepage = 'www.247realmedia.com/site-demo-formats/1',
        OAS_listpos = 'x25,x30',
        OAS_query = '',
        OAS_rns = (Math.random() + "").substring(2, 11);
 
    document.write('<script type="text/javascript" src="' + OAS_url + 'adstream_mjx.ads/' + OAS_sitepage + '/1' + OAS_rns + '@' + OAS_listpos + '?' + OAS_query + '"><\/script>');
</script>
 
<!-- Ad Calls -->
<script type="text/javascript">OAS_RICH('x25');</script>
<script type="text/javascript">OAS_RICH('x30');</script>
  1. All logic used to detect ancient browsers and JavaScript 1.1 has been removed.
  2. Because OAS_target applied only to the fallback simplified implementation, that was removed.
  3. Line 10 provides a fallback for browsers that don’t support the random method, so that was removed.
  4. Lastly, the function name for the ad calls was changed from OAS_AD to OAS_RICH since we no longer need to support an alternative to the OAS_RICH function (defined in the script returned by the main call to the ad server).
  5. Update: In the comments, kangax pointed out that the line producing the random number could be further improved by simply type converting the number instead of creating a String object. He also mentioned that we can remove the split of the opening script tag.

The set-up code can be reduced even further if you’re not using query strings to pass keyword data to your campaigns.

My tests revealed no problems with the revised version but I strongly recommend conducting your own thorough tests before making major changes to your ad code.

While these changes do not address the main performance problems of the ads—namely, document.write— they do clean things up significantly. And I think that’s always a good start.

Leave a comment

Discussion

  1. Andy Davies
    March 23rd, 2010

    Out of curiosity…

    Why is the <script> tag split in the document.write?

  2. Billy Hoffman
    March 23rd, 2010

    Rob,

    Great article! Anything that gets rid of 14 year old JavaScript is a good thing.

    I believe getting rid of the document.write() shouldn’t be that hard. It seems the reason it is used is to block the browser while it gets the [SCRIPT] from the ad server to make sure the code is available before calling the OASRICH functions. If you created a function that called the OASRICH functions that runs on window.onload, you should be able to make the request for the ad JavaScript be async.

  3. Will Peavy
    March 23rd, 2010

    Nice article. I’m in the process of cleaning up an OAS script too. I’m using createElement() and setAttribute() in place of document.write() and it has performed well in my tests so far.

  4. Rob Flaherty
    March 23rd, 2010

    @Andy
    The script tag is split like that to prevent the browser from parsing the string as a script element. I’m actually not entirely sure that it’s necessary anymore.

    @Billy
    Thanks!

    It’s the use of document.write in the OAS_RICH functions that is the obstacle, so calling those functions on window.onload unfortunately wouldn’t work. (Sorry, I realize I did not explain the problem clearly in the post.) But I think there’s still a way to do it… subject for a future post. =)

    @Will
    That’s great. Are you using the MJX method or one of the others? How are you working around the document.write insertion code that the ad server returns?

  5. "Cowboy" Ben Alman
    March 23rd, 2010

    You know, Rob.. I recently attempted to “help along” remotely loaded scripts that use document.write with a little jQuery plugin called “loadAdScript”:

    http://benalman.com/projects/jquery-misc-plugins/#loadadscript

    It appears to work well in testing, but I haven’t yet been able to test it out in production.

  6. ddunlop
    March 23rd, 2010

    A year or more ago I played with the idea of asynchronously loading the mjx call and overriding the document.write function so that it would queue up html into a variable until I flushed it out and it would work for simple ads but once you start trying that with ads that write out their own script tags with document.writes in them it got messy since some browsers wouldn’t load those scripts until later (I had to parse out the script tag and load it via dom insertion).

    Another idea I had was to load the mjx tag at the bottom (allowing the content of the page to load and become usable while loading the ads late) and write ads into divs that could then be moved into place. I didn’t really play with this out of fear of expandable/pushdown ads.

  7. Rob Flaherty
    March 23rd, 2010

    @Cowboy
    That’s great! It sounds like just what I was looking for. I’m looking forward to checking it out.

    @ddunlop
    I think you identified the biggest hurdle, 3rd party tags that use document.write to insert their own scripts. That’s the thing that I fear makes serving the ads asynchronously near impossible. Although it sounds like you got pretty close to a solution!

  8. kangax
    April 10th, 2010

    You can shorten it further by replacing `new String (Math.random())` with `Math.random() + ”`. No need to create a *string object* just type convert number to string :)

    I would also wrap this whole thing with self-executing function and gotten rid of those “OAS_” prefixes in variable names, but that’s not a big deal.

    Also, AFAIK, there’s no need to split start `script` tag; it’s end tag that needs to be escaped (well, technically—as per, say, HTML4.01— script element is terminated on occurrence of “</" but none of the browsers I've seen respect that, and instead close it on "</script"). And you already have end tag escaped properly as "”.

  9. Rob Flaherty
    April 12th, 2010

    @kangax

    Thanks for the recommendations! I updated the post to reflect most of them. I agree that wrapping everything in a self-executing function and shortening the variable names is a good idea, but, in the context of the blog post, I thought it’d be better not to alter the variable names to avoid any confusion with the OAS documentation.

  10. Markus Meineke
    April 14th, 2010

    Hi,
    thanks for the article.
    I’m currently working also on the document.write problem and have found a good solution for me.
    As mentioned already by others, I load the advertising at the bottom of the site.
    For this I have written a php function that I run at the points of the site. This function writes an ID in a global array and echos a container with this id. Like this:
    At the bottom, I create a container for all the ads again, in that the javascript code is written. Then I go through all containers in a loop, remove the javascript and set the ads to the right place.

    The solution increases the loading speed of the site many times over.

    I hope someone helps this solution and you can read this text. Since my English is not so good, I translated this text with a google. ;)
    If you click on my name, you can see my solution in action.

  11. Rob Flaherty
    April 15th, 2010

    @Markus
    Thanks for the information. I’m still not sure how you get around 3rd party ad script that includes document.write. I’ll take a closer look at your site to see if I can figure it out.

  12. Mathias Bynens
    April 18th, 2010

    Wow, nice work!

    Another optimization would be to omit type="text/javascript" from the JS code. This is safe because all browsers default to it anyway. (Also, in HTML5 this is optional anyway.)

    Also, why are the ‘ad calls’ in their own script block? Why not just add them in the same block as the rest of the code?

    FYI, you might be interested in this — I recently optimized the asynchronous Google Analytics snippet.

  13. Rob Flaherty
    April 20th, 2010

    Mathias,

    Good question about the ‘ad calls’. I didn’t make it clear in the post, but they occur later in the page, where the ads themselves are placed. The OAS_RICH function uses document.write to insert the ads into the page.

    Thanks for the tip about omitting the type attribute. I seem to remember reading that there was some debate about whether or not leaving off the ‘text/javascript’ was safe?

    Just a left a comment on your Analytics snippet post…

  14. Mathias Bynens
    April 20th, 2010

    Robert,

    I understand the ad calls are separately placed in the document where the ads should occur. Still I think you can include the first ad call in the main script block already, if you just place it where the first ad should appear, since the snippet is using document.write() (which blocks further script execution until the file is loaded anyway).

    As for the safety of omitting the type attribute — I’d be very interested to see that discussion. As far as my tests go, everything seemed to work perfectly without it in all A-grade browsers.

  15. Rob Flaherty
    April 21st, 2010

    Ah, I see what you mean. I agree that would work, but for the sake of clarity, I didn’t want to stray too far from the implementation code as it’s presented in the OAS documentation.

    Now that I think of it, the debate I referred to about the type attribute was between you and kangax on his blog. =)

  16. Wilf
    June 10th, 2010

    You can definitely use some post-loading techniques. Make emtpy divs on your page with ids of ad_pos_1, etc and then place this as your last content on the page before .

    OAS_RICH(‘x25′);

    document.getElementById(‘ad_pos_1′).appendChild(document.getElementById(‘ad_1_hidden’));
    //repeat for each ad

    I’ve seen this working in production environments very successfully. If you have further javascript below this, it will get blocked whilst this executes, however at least your page has rendered already.

    Causes slow ads to appear out of nowhere, but at least the users can see the content.

  17. Rob Flaherty
    June 13th, 2010

    @Wilf
    Yes, the method you describe works but it’s not actually post-loading the ads, which I think is the ultimate goal. Still, I agree it’s an improvement. As ddunlop mentioned previously, one concern would be possible complications with push-down and expandable ads.

© 2009 ravelrumba  /  web design by ravelrumba

hosted by WebFaction