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. 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.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(...);
document.body.appendChild(...)
or am I missing something ?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.
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!
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>
“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.
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
If you change to “all the browsers don’t implement” then your subject becomes clear.
Fixed. Thanks!
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
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.
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.
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 ?
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.
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.
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
application/xhtml+xml
. If this is not set, your content is being interpreted as html.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...
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