It’s just data

That’s Not Write

I learned something new today.  I’ve known for a while that pretty much all the browsers don’t implement document.write when found in the context of XHTML documents — even in the case where both the enclosing document and string are separately well formed.  Because Google AdSense depends on document.write, the net result is that I only serve ads to users of browsers that don’t support XHTML, which increasingly means that only IE users see ads.

The solution is to use createElementNS instead.  So far, so good.  The only piece left to the puzzle is where to append the child that you created.  If you simply do a document.appendChild, the new element ends up at the end of the document.  There doesn’t seem to be a property which indicates the current node in the tree at the time of the parse.  But in cases like adsense, you generally want the widget put in place.

Not seeing an answer online, and not finding the answer in the DOM, I sought out Ian Hickson in IRC.  When I posed the question, he answered without a moment hesitation:

var e = document.getElementsByTagName('*'); var pos = e[e.length-1]; pos.parentNode.appendChild(...);

The reason this works is the difference between a stream of bytes and a tree, namely the DOM.  Appending a child to the root effectively places the node after the body.  You need to drill down to find the last node based on an in-order traversal of the DOM, and append the child to the right of that one.

Now that I understand it, the following seems both more clear, and more scalable for large documents:

var pos = document;
while (pos.lastChild.nodeType == 1) pos = pos.lastChild;
pos.parentNode.appendChild(...);

So are we to expect XHTML-compliant serving of AdSense ads here soon?  Or is this merely intended as a suggestion on how Google needs to fix their code?

Posted by Scott Johnson at

Sam,
If it is for XHTML document, document.body  can be use rite ? Like document.body.appendChild(...) or am I missing something ?

Posted by Saravanan at

Scott: I have no insight into Google’s product plans.  All I know is that they could fix this if they wanted to.

Saravanan: you have to picture a tree.  In the drawing on the right I’ve drawn a simple DOM (omitting attribute and text nodes).  The red box is the body, the blue box is the script.  document.body.appendChild would create the green outlined box.  What you want instead is a box which is immediately adjacent to the blue box.

Posted by Sam Ruby at

document.write and xhtml

Sam Ruby knew that document.write doesn’t work well with XHTML so he went to find the best solution. His context: Because Google AdSense depends on document.write, the net result is that I only serve ads to users of browsers that don’t support...

Excerpt from Ajaxian at

I have no insight into Google’s product plans.  All I know is that they could fix this if they wanted to.

Think of all the money they’re leaving on the table!

Posted by Robert Sayre at

btw, “Hixie” is a nickname... his real name is Ian Hickson. (not Ian Hixie)

Posted by Greg Stein at

Sam,
Thanks for explaining with picture. I was running your script for appending element in FireBug’s console window against this blog page. I found that it is failing if the XHTML document ends with space, new line or if body’s last child(tags like img, br, script) does not have any child node associated with it.
Examples,
1. XHTML document ends with </body></html> [Note the space after html tag.]
2. XHTML document ends with
</body></html>
3. XHTML document ends with <img src="someimage.jpg"/></body></html>
4. XHTML document ends with <script></script></body></html>

Even Hixie’s script didn’t produce desired result for this page when I added span tag to the page. Although the result was theoretically correct.

For this page It Hixie’s script produced
<a href="http://www.atomenabled.org/">
  <img src="http://www.intertwingly.net/images/atom-logo75px.gif" alt="Atom Enabled"/>
<span>Some Span Text to test</span>
</a>

But I would expect
<a href="http://www.atomenabled.org/">
  <img src="http://www.intertwingly.net/images/atom-logo75px.gif" alt="Atom Enabled"/>
</a>
<span>Some Span Text to test</span>

Posted by Saravanan at

“Hixie” is a nickname... his real name is Ian Hickson. (not Ian Hixie)

Fixed.  Thanks!

I was running your script for appending element in FireBug’s console window

I should have been more clear.  I was looking to be able to support being able to add the following at an arbitrary point in a document, and have the script referenced insert elements at the point of the script:

<script src="http://example.com/foo.js"></script>

When running in the console window, you may be referencing a completely loaded page.

Additionally I’d want to do so in a way that works with both text/html and application/xhtml+xml, and both with browsers that support DOM level 2 and those that don’t.

Hopefully, I’ll have something to show in a few days.

Posted by Sam Ruby at

links for 2006-11-12

Sam Ruby: That’s Not Write Method of determining where the hell I am in the DOM tree so that one can use appendChild. God this is going to save me a lot of agita. (tags: dom javascript document.write) Websense® - Blog: Malicious Website / Malicious...

Excerpt from ed costello: comments and links at

Sam,

In both NetNewsWire and Safari, I’m seeing the first line of your post as


I’ve known for a while that pretty much all the don’t implement document.write when found in the context of XHTML documents

Which was confusing, and I had to read further on in order to figure out what you were talking about.  If you change to "all the browsers don’t implement" then your subject becomes clear.

--Dethe

Posted by Dethe Elza at

If you change to “all the browsers don’t implement” then your subject becomes clear.

Fixed.  Thanks!

Posted by Sam Ruby at

XHTML, document.write, and Adsense

After some recent discussion concerning the use of document.write() in XHTML documents served with the doctype “application/xhtml+xml” I decided to revisit the problem. An issue with the solutions proposed by Sam and Ajaxian is that they aren’t really ...... [more]

Trackback from John Resig

at

Overwriting document.write:

try {
  document.write('');
  var nav = navigator.userAgent.toLowerCase();  
  if (nav.indexOf('opera') > -1 &&
    nav.indexOf('7') == -1) 
    throw new Error();
}
catch(e) {
  document.write = function() {
    // Concatenating all arguments
    var str = ‘’;
    for (var i = 0; i < arguments.length; i++)
      str += String(arguments[i]);
    var s = document.createElement('span');
    s.innerHTML = str;
    var e = document.all || document.getElementsByTagName('*');
    var last = e[e.length - 1];
    // Put everything in a span in order to execute included scripts
    last.parentNode.appendChild(s);
    // taking it out again to have “pure” innerHTML
    last.parentNode.removeChild(s);
    last.parentNode.innerHTML += str;
  }
}

A Test:

document.write('Yes<script type="text/javascript" src="included.js"></script>');

Normal document.write works now in XHTML mode in very browser exc Netscape 7 / Firebird.

Andi

Posted by Andreas Kalsch at

Aaah execution order makes a difference. Thanks Sam for patiently explaining this.

Posted by Saravanan at

document.write and xhtml

Sam Ruby knew that document.write doesn’t work well with XHTML so he went to find the best solution. His context: Because Google AdSense depends on document.write, the net result is that I only serve ads to users of browsers that don’t support...

Excerpt from Ajaxian at

Sam Ruby - That’s Not Write: "I’ve known for a while that pretty much all the browsers don’t implement document.write when found in the context of XHTML documents — even in the case where both the enclosing document and...

Excerpt from Tim's Weblog at

Sam Ruby: That’s Not Write - document.write + xhtml

[link]...

Excerpt from del.icio.us/tag/xml at

The only right thing for AdSense to do, is to be implemented as a sort of microformat, with a specific class name on any type of element. Then, the AdSense script can be included in the head section of the document and a <div class="adsense"></div> can be placed anywhere in the body section. This also scales well if you want different types of ads, because you could just add different class names to the div which can be catched by the AdSense script.

It would not take me more than a couple of hours to rewrite te AdSense script to produce XHTML-compliant code and at the same time be backwards-compatible with older browsers and tagsoup HTML documents. Google should be ashamed of themselves for producing code of such poor quality as this. But that goes for all of their web based services.

Posted by Asbjørn Ulsberg at

More news from around the web

Sam Ruby and Leonard Richardson are writing a book comparing Web Services to RESTful services. We want to restore the World Wide Web to its rightful place as a respected architecture for distributed programming. We want to shift the focus of...

Excerpt from Category 4 Blog at

Mobile Javascript & DOM

In preparation for the next generation mobile browsers where Javascript and the DOM will have a higher profile I have been doing some R&D about what is, and isn’t, possible. Some general lessons learned thus far: (1) Avoid using...

Excerpt from Paxmodept Blog at

Sam,
nice findings...but shouldn’t the check be modified in:

while (pos && pos.lastChild.nodeType == 1) pos = pos.lastChild;

this will avoid an error produced by checking a property on a
“null” pointer reference in the last iteration of the while loop.

And for the above reason it seems to me that your code is trying
to append to “null.parentNode”. I am a bit confused, also I have
come to know that things like this may still work in IE :-)

After this mod, be sure to append to “pos” or it’s parent depending
on your needs. I didn’t run any test really, I just recognized some
code pattern I am using in the DOMLoaded / onload problematics.

Posted by Diego Perini at

Diego: in general, yes.  In this case, I know the node is a <script> element, and therefore an element.

Posted by Sam Ruby at

Sam,
I was looking at this code in a moment where I do not have the BODY yet !!!

That explains everything, you are running this when the BODY already
exists after the “onload” event. If the BODY is not there it will fail.

You see, I am trying to solve the same issue at another point,
during the browser parsing when the page is loading and I am
doing things before the BODY is being inserted in the DOM.

...DOMContentLoaded...remember ?

Posted by Diego Perini at

I don’t see any problem with AdSense and XHTML 1.0 Strict.

‘document.write’ is not a part of XHTML specs, and not mentioned here <iframe> tag is not a part of XHTML 1.0 Strict. However, it does not prevent a browser to do some kind of XML Transformation, such as transform XHTML 1.0 Strict-compliant XML to another XML with additional <iframe> element, and then execute SGML (whatever).

Google does not use ‘document.write’ embedded into XHTML:
<script type="text/javascript"
  src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>

All this staff is executed during/after DOM is constructed (and validated against schema), and JavaScript simply modifies DOM (it is possible with any kind of other technology too, such as XSLT, C++, Java, ...)

I run successfully AdSense with XHTML 1.0 Strict during a month, sorry for not having a chance to check Mime at HTTP-levels.

Yesterday I had a problem with my new site, IE-6 didn’t show a whole page after I enabled AdSense. However, I didn’t have any problems with Mozilla/Firefox. I am going to enable AdSense again, after some fixes (mostly in CSS).

Common solution with AdSense: I put it inside <div id="AdSense"> element.

Posted by Maestro at

overwriting document.write can be helpful (regardless of xhtml, you pretty much have to do that if you want to bring in stuff other people wrote after the onload event, if those other people used document.write), but in the general case document.write operates at a lower level of granularity than innerHTML+=

innerHTML wants to spit out completed DOM elements, while those can be split across multiple document.writes.

innerHTML+= loses state associated with any object/embed stuff you are doing, if you care about multimedia.

Fortunately, most people don’t care about these limitations — let alone legacy javascript.

Posted by rdmiller at

Hi!

I’ve heard that document.write is not allowed in html with type XHTML,
but seems it works anyways on my FF 2.* IE 6. I tried making test ASPX page (it is rendered as XHTML) and everything worked good.

Could you explain that ? Browsers don’t follow specifications ?

Thanks in advance,
Alex

Posted by Alex at

Try setting content-type to application/xhtml+xml.  If this is not set, your content is being interpreted as html.

Posted by Sam Ruby at

document.write and XHTML

For a while now, my website is using XHTML. That by itself is working fine. However, recently, I decided to actually set the appropriate MIME type for browsers that understand it (pretty much every modern browser except IE...) The PHP code looks...

Excerpt from UCI Housing Network and Cable TV Issues at

This is a little late to the game but maybe this will help somebody else out there. This worked great for an xml/xslt site in doing google adsense...

[link]

in combination with...

document.write=function(e){innerXHTML(IDorElement,e,true);};

and or this method

document.write = function(str,IDorElement){
  var thirdParameter = true;
  if(!IDorElement){
var scriptElements = document.getElementsByTagName("SCRIPT");
var currentScript  = scriptElements[scriptElements.length - 1];
var parentElement  = currentScript.parentNode;
//var nextElement  = currentScript.nextSibling
thirdParameter  = currentScript; //nextElement;
IDorElement  = parentElement;
  }
  innerXHTML(IDorElement,str,thirdParameter);
}

hope this helps

Posted by cabrera at

Painful iframe resizing or how to fit ad banners into ajax heavy web sites with writeCapture

Or: The crux with online advertising The online advertising industry ......

Excerpt from return1 at

Add your comment