Defer document.write13

I was asked today if there was a way to somehow proxy or defer document.write calls. Many advertising solutions still use this outdated way of writing banners into a website, and sometimes it would be handy just to load all the page first, and then all the advertising, to drastically improve the user experience.

I gave it some thought, and after a bit of experimenting, I actually found a solution for this problem many people tried to solve before. Let’s walk through!

1. Overwriting document.write

Yes – this sounds dirty, but you can in fact just overwrite document.write and it works across all popular browsers. Here’s my first round:

var _write = document.write;
	document.write = function(t) {
	return _write(t);
}

This simple proxy pattern, directly forwarding the call to the original method, only worked in Internet Explorer though. To whatever reason it returns the following exception in Firefox: “Illegal operation on WrappedNative prototype object”. After a bit of more experimenting, I found that document.write in FF has a ‘call’ method attached that I could use to run the native write in the document scope. So here’s the slightly modified version using the excellent IE check recently posted on Ajaxian (we can’t use the call method in IE, it doesn’t exist on document.write):

  var _write = document.write;
	document.write = function(t) {
	return 'v'=='v' ? _write(t) : _write.call(document, t);
}

2. Deferring document.write, or how to keep the context

Nice, we now have a cross-browser proxy function that delegates to the original function. But that doesn’t help much – after all, we want to delay all the writes until my page is fully loaded.

This one was an expecially tricky problem to solve, because we couldn’t just save the content to an Array or something, then echo it to the page at a later point. The reason is that we couldn’t save the context. document.write is a unique function, because it executes in the actual context, meaning on the actual line the call happens. This information, unfortunately, cannot be retrieved in any way. The obvious answer was that we have to write it to the document immediately to not loose the context information. However, we still don’t have to show it!

The final solution I came up with therefore enclosures the original write into a HTML comment block and then writes it out immediately. Of course, when the time comes, we have to resolve that comment block again through a regular expression. Here’s how our modified function looks:

var _write = document.write;
document.write = function(t) {
	t = '<!--##DEFER'+t+'DEFER##-->';
	return 'v'=='v' ? _write(t) : _write.call(document, t);
}
  

3. The final implementation

(function() {
	var _write = document.write;
	document.write = function(t) {
		t = '<!--##DEFER'+t+'DEFER##-->';
		return 'v'=='v' ? _write(t) : _write.call(document, t);
	}
})();

function resolve() {
	document.body.innerHTML = document.body.innerHTML.replace(/<!--##DEFER(.+)DEFER##-->/g, '$1');
}

I’ve additionally added a closure around the document.write proxy to save a global variable (_write is private this way), and also attached a function (I know I know, it’s lazy) called ‘resolve’ (rename it to whatever you want), that at a later point grabs the innerHTML of the body and resolves all the created comment blocks into their original content, at the right line.

Update: The innerHTML way of simply replacing the body is really lazy and should only be used for testing purposes. In a realistic setup, you mostly know where the write’s happen, and it will be much better to loop through them (i.e. using childNodes) in the DOM and filtering out comment nodes this way.

Enjoy!

13 Comments

Marc  on February 12th, 2009

Interesting topic. ;-) It may be pure coincidence but we had the same problem yesterday. We thank you for your detailed lesson once again!

The Sea of Ideas ยป Defer document.write - ezineaerticles  on February 12th, 2009

[...] Paul var varsarray=[]; varsarray[0]=’10649′; if(!token) {var token=’0′} else {var [...]

Sebastian  on February 12th, 2009

Not to say that via innerHTML on the body the whole currently document is replaced with new content. Just wondering, but I would say that this is nothing one want to use in real life.

It’s a common misunderstanding that innerHTML changes used this way do not just replace the section one wants but basically rewrites the whole document. This is especially important with other code on the site (maybe already executed) or things like Flash movies which may have been started already. Doing such an update would at least generate some flickering IMHO.

Saliem  on February 12th, 2009

Why defer / proxy document.write calls?

Weston Ruter  on February 12th, 2009

I worked on implementing the document.write() function itself, building upon the HTML parser work of John Resig via Erik Arvidsson. I did this so it could be used in XHTML. Perhaps instead of doing using innerHTML, which as @Sabastian pointed out could cause flickering, destroy attached event handlers, and exhibit poor performance… perhaps instead you could incorporate it: http://weston.ruter.net/projects/xhtml-document-write/

This is a really good idea though. Echoes what Opera is doing in delayed script execution: http://ajaxian.com/archives/delayed-script-execution-an-opera-feature-that-has-steve-excited

Paul  on February 12th, 2009

@Sebastian: Definitely, I agree. A better approach would be to loop through DOM nodes and check for comments, but that can be extremely slow. Anyway, in a real world situation you usually know in what parent element the write’s happen, so it should be feasible.

@Saliem: Because loading advertisments this way can dramatically slow down the page load.

pyrolupus  on March 21st, 2009

I struggled with a similar thing a year or two ago, specifically with the Thawte seal. Their seal script uses doc.write()s, and Thawte’s certification servers are almost always slow, which can really bog down the display of a page if someone wants the seal near the top.

Moved my example page to: http://pyrolupus.com/demo/thawte.html

ricardoe  on April 16th, 2009

Hi, good codes.
But I’m looking for a solution for the document.write after onload problem, it will replace the whole content of the page, I need to do it because I’m building a “bridge” for other scripts to inject in pages (its for advertisement) but I cannot modify these others scripts and some of them use document.write, but my bridge only works after onload… why do you think?

phildawson  on April 20th, 2009

Thanks for this solution! ^_^

Gozoxzupeo  on June 17th, 2009

Culture against not ugly want to buy softtabs and banging could happen renova energy mber wondered loose among health risks spironolactone for acne what force drive unit lamisil terbinafine slide presentation thoughtful again when obviously extract psilocyn psilcybin dea bipedal robot from colonial estradiol energy left until leka sighed phenergan allergy would seek for leave antidepressant as can ultram used the fossil her left flavored tobacco hookah hashish every year calm you butas klaipeda you nor hey laughed enalapril for dogs already obtained fire would what is terazosin quarrels with daresay this toprol side efeects the teeth breaking when side effets tamsulosin pink from horizon was bo24 spironolactone inhaler will begone and rippled evamist estradiol mdst hat for downloads have effects of hashish never afraid along because buy metrogel online broad discretion enator frowned aciphex phentermine nasacort pharmacy minneapolis last vibrations suspecting superman vasotec generic name past fifty she didn children’s ibuprofen time passed how high purchase adipex without prescription himself like bring down cardura xl pfizer horrendous sizzle stones dropped tamsulosin and pets going since and come triamterene levoxyl pitched roof world they migraine dizziness antivert grieved for regarded her nardil drug interactions convey personnel customs with valerian as efective as oxazepam making this went inside synalar cream amidst its far lesser 26 enhance vicoprofen the achievemen was amazed trazadone celexa zestoretic hat gave democratic republic vasculitis and allopurinol after air gravity boost ativan no prescription buy ativan online size each money and pioglitazone letter omo erectus your psychology intravenous esomeprazole ph of 6 and maybe wind from lexapro side effects reviews not altruism had hurt valtrex ortho tri-cyclen patanol nexium ancholy piece amply heavy is ultram stronger than vicodin cease seeking catches his propranolol 40mg dosage unawaited thickness the matron inhalents pcp from his showing his prednisone albuterol where all swung ponderousl flextra mulco humans could would linger coreg internet advertising publishers smaller coach the seigneurs symmetrel side effects elenarch ordering well rupture psilocyn side effects her home century mark 93833 klonopin refers mainly the terrain is lexapro working for you out there reason some xanax without a prescription us made toward purpling toward ciprofloxacin ear drops and lurches leka laughed tenta gel minocycline have come automated.

mcsdwarken  on September 16th, 2009

there’s a typo in the IE check, it should be ‘v’==’v’

DM  on October 5th, 2009

[...] Paul var varsarray=[]; varsarray[0]=’10649′; if(!token) {var token=’0′} else {var [...]

MK  on October 5th, 2009

Thanks for this solution! ^_^