Friday, January 05, 2007

The Tale of Two Document

This afternoon, I spent three hours figuring out what's the different between window.document and content.document from Mozilla or FireFox extension's point of view. Figuring this out solves lots of my questions or "why the heck this doesn't work" wonders. This article explains what are the differences, when to use which and ends with some very practical kludges that I've found quite handy.

Document? Which document?
My previous Ajax experience told me that document is the HTML document, aka the DOM model, which I could modify the node attribute, change element layout, or remove one. When I write document.getElementById('sidebar'), I mean find an element whose id is sidebar. This is the document. But, in the Mozilla's world, this could cause you big trouble as I did.

In Mozilla's point of view, the whole browser is a rendered DOM tree. Which means that the menu, the toolbar, the status bar are all elements on a DOM tree. When you add a toolbar, you're inserting a toolbar element into the DOM tree. When you see the status bar, you're actually looking at a label. This is the beauty of XUL and Mozilla, the extensible architecture where other wonderful technologies like RDF, XBL, and overlay. Now when you write document.getElementById, the document is not referring to the HTMLDocument. But it's referring to XULDocument, which represents the current browser window, aka the chromeWindow.

Here is a concrete example. Run this and you'll see a "hello" showing up on your status bar. window.document.getElementById('statusbar-display').label="hello"; If you want to try this out, paste this into your Javascript Environment which comes along with FireFox Extension Development add-on. If you don't have it, get it now.

The statusbar-display is the left most status pane in your firefox, where you could find "Transferring data from ...". So here the window.document points to the current ChromeWindow's document, which is the XULDocument. Your HTML document doesn't and probably won't have an element with id like that.

Then, how could I get a pointer to the HTML document? Use window.content.document. If you see _content, it's an obsolete way of representing the HTML DOM tree. As a result, in order to find the sidebar in the current HTML document, you need to write the code like this: window.content.document.GetElementById("sidebar");, where the content means the real content instead of the user interface.

This script explains it much better:
print(window);
print(window.content);
print(window.content.document);
print(window.document);

Here is the output:
[object ChromeWindow]
[object Window]
[object HTMLDocument]
[object XULDocument]


Shorthand for window.content vs window.document

  • window.content can be written as content when there is only one window. The window is the current window or frame which the document is contained. If you are working on a multi-frame document, you'll have to write code like framelist[i].document.
  • window.document can be written as document because of the same reason explained above.
Event Handling
In Ajax, if we want to install an event handler for keypress event, here is what we'll do: document.onkeypress = function(event) { ... }. But in Mozilla world, you can't hook the event up with HTMLDocument directly. This works find in the Javascript Environment, but it just doesn't work in your extension javascript code. (My environment is Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1). To get it working, you will have to hook the event handler up with window.document.

One side effect of this is that now the event will be triggered for both the content in HTML and the controls in the browser's toolbar, status bar. If your event handler is to delete the element by clicking on it, you could delete the toolbar buttons one by one by clicking through the buttons!

I didn't find a good or official solution to this problem so if you know a better way to do this, please definitely let me know. Here is my kludge which just works.
function onclick(event) {
if (event.target.toString().indexOf('XULElement')>=0) return;
// now, do whatever you want.
}

No comments: