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>
  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>OAS_RICH('x25');</script>
<script>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.

Both comments and pings are currently closed.

Discussion

Out of curiosity…

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

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.

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.

@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?

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.

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.

@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!

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 "”.

@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.

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.

@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.

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.

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…

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.

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. =)

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.

@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.

Victor

As the “time-machine” is back with IPhones and Ipads… does this refinment to show only rich media still apply?

I would strongly caution against the use of Willf’s suggestion to move the ads after they’ve been loaded — Gecko and WebKit will reload an iframe when it’s moved within the DOM. This is going to lead to double-counting and an increase in overall load times. http://digital-fulcrum.com/iframe-reloading-behavior/

Take a look at ghostwriter which provides asynchronous loading of scripts that use document.write. With it, you can post-load these ads pretty easily.

Elizabeth

@Wilf
I’m new to this… I assume when you implement this method, you are using jsx instead of mjx.
>> document.getElementById(‘ad_pos_1′).appendChild(document.getElementById(‘ad_1_hidden’));

if ad_pos_1 is the container where the ad should show up, what would ad_1_hidden mean here? Please let me know, I need to implement oas using jsx method.

Thanks very much for this solution. I got the mandate today to implement this OAS code and when I looked at it, I felt like I it was the mid-90′s again. I used your solution and it’s working fine.

I want to respond to the post from Will Alexander. First, if you’re going to promote a paid service (ghostwriter) that you work for, you *have* to disclose it. Otherwise, it’s just free advertising and not above board.

Second, your comment about iframe may be valid, but this ad server does not use an iframe. It’s possible that a given implementation might use that HTML tag, but mine certainly does not. So, you’re spreading FUD for no good reason.

Peanut Gallery

@BillC I think you are mistaken.

Ghostwriter starts at 10,000k for one site (price not posted on their site, by the way), so he has 10,000 very good reasons, and he get a couple link backs to the site of which he is a “Partner”.

It’s not above board and speaks volume about the company’s ethics.

However, this article was great and its nice to see developers helping developers purely out of fraternity.

Has anybody tried using the SX tags and iFrames? I’m thinking about dynamically creating the iframe and inserting it into the mark up after the page has fully loaded. What is the downside of doing it this way?

@Benny,
Sorry for the late reply. I haven’t tried using SX and iFrames, although I have experimented with the idea of using iFrames with the MJX tags. It’s a little janky but I believe it works. If anyone is interested in it I could write up a quick blog post about it.

We have built a script that uses the SX tags and Iframes to async load the ads. Works well but found an issue using companions with the SX tag, this is from realmedia,

“companions not displaying together
all the time is due to something called a race condition that can have an
affect on the coordination of SX tags”

Because the SX tags don’t do a single call to all of the ads on the page like the MJX do.

I know this is a bit old but I would like to give my 2 cents here:

you should be able to use SX tags and still be able to use companion positions as long you declare all positions on each tag, ie:
Banner 1 – @Top,TopLeft!Top
Banner 2 – @Top,TopLeft!TopLeft

have you tried that????

cheers

PC

Great article. I love the idea of tackling advert serving code rather than leaving it to rot, as so often happens. The problem with legacy code is that; as it gets older, and the issues it was designed to address become forgotten about, it becomes even harder to understand and developers are even more worried to touch it in case they break something.
I’m glad though that you emphasise the importance of testing after refactoring. I don’t think that can be emphasised enough!