Sunday, January 18, 2009

Using an IFRAME to force a doctype

There have been times when I wanted to create a JavaScript widget that I could embed into any web page. One problem I ran into was that I would develop my widget in a standards mode page, but then it would not work when embedded on a page in quirksmode.

"Who still uses quirksmode?" you might ask. Well, for legacy reasons, iGoogle gadgets do. I thought that OpenSocial fixed that, but item 6 of the spec says otherwise, though the discussion group suggests a distasteful workaround.

Either way, this whole doctype thing is a problem. On a web page, the document has a doctype property, but it's read-only, so trying to edit that is a dead-end. Originally, I tried manipulating the content of an iframe with src="about:blank" which I thought would be clean because it had no content, but no content means no doctype, so it is also stuck in quirksmode.

One option that does work is to have an empty html page with the strict doctype on the same domain as your JavaScript widget. That way, your widget can write its content into a local iframe using the empty html page as its source. The drawbacks to that approach are: (1) it complicates the API of your widget because it requires the consumer of your API to make a server-side change and bake the URL into calls to your API; and (2) you have to add some extra logic to listen for the iframe to load before you can write to it, so it forces your API to become asynchronous rather than synchronous.

The best solution I've seen so far is to dynamically create an iframe with no src attribute and to use document.write() to insert the content of the iframe or to set the src attribute to javascript:parent.functionThatReturnsTheDesiredContentAsHtml(). This test page demonstrates both techniques.

One thing that is particularly advantageous with this technique is that if your widget requires its own stylesheet, it can load it in the iframe instead of the top-level page where your CSS class names run the risk of conflicting with the CSS class names used in the host page. So even if you know that your widget is going to be used in a standards-compliant page, you may want to use this technique to create a "fresh namespace" for your CSS.

The only disadvantage that I've found with iframeing over inlining is with managing overlays that need to appear outside the iframe. In the test page, there is a green box that appears over the iframes and the main page. For it to be visible, it needs to be an element of the top-level page, which (1) may be in quirksmode and (2) will not honor the CSS rules defined in your iframe. That means that you may have to design your popup so that it works in both rendering modes and uses inline styles (exactly what we were trying to avoid with our universally-embeddable JavaScript widget!).

Also, the JavaScript code that manages such an overlay needs to be wary of its use of the document variable as it is important to use it in the correct context (iframe vs. host page). It is best to create different getters for the two documents and to use those exclusively, avoiding direct access to document altogether.

Overall, I am still pretty happy with this solution, though one thing that occurred to me is that this is a little frightening with respect to phishing in that a malicious web page could easily display an iframe to foreign content (that is familiar to you) and then display its own login box or credit card form on top of it to lure you to enter your information. I mentioned this to Mihai, and he said this is somewhat of a known issue, citing this example at zombieurl.com (warning: page contains sound). I guess that goes to show that you can never be safe from zombies, not even on the Internet.