Introduction

One of the greatest strengths of Firefox when it comes to DOM scripting is that it exposes all of its interfaces to Javascript, allowing you to enhance and extend. Making Firefox support IE's method of event handling is as easy as:

HTMLElement.prototype.attachEvent = function(type, listener) { return this.addEventListener(type.substr(2), listener, false); }

While using this as an excuse to write non-forward compatible code is a bad idea, the ability remains as immensely useful.

As it turns out, Opera 8 exposed constructors in the same fashion as Firefox, leaving us only Safari and Internet Explorer to contend with. You may download a zip file of all the source code involved, or view it below.

Safari

/* HTMLElement Prototyping in KHTML and WebCore Copyright (C) 2005 Jason Davis, http://www.browserland.org Additional thanks to Brothercake, http://www.brothercake.com This code is licensed under the LGPL: http://www.gnu.org/licenses/lgpl.html */ if (navigator.vendor == "Apple Computer, Inc." || navigator.vendor == "KDE") { // WebCore/KHTML (function(c) { for (var i in c) window["HTML" + i + "Element"] = document.createElement(c[ i ]).constructor; })({ Html: "html", Head: "head", Link: "link", Title: "title", Meta: "meta", Base: "base", IsIndex: "isindex", Style: "style", Body: "body", Form: "form", Select: "select", OptGroup: "optgroup", Option: "option", Input: "input", TextArea: "textarea", Button: "button", Label: "label", FieldSet: "fieldset", Legend: "legend", UList: "ul", OList: "ol", DList: "dl", Directory: "dir", Menu: "menu", LI: "li", Div: "div", Paragraph: "p", Heading: "h1", Quote: "q", Pre: "pre", BR: "br", BaseFont: "basefont", Font: "font", HR: "hr", Mod: "ins", Anchor: "a", Image: "img", Object: "object", Param: "param", Applet: "applet", Map: "map", Area: "area", Script: "script", Table: "table", TableCaption: "caption", TableCol: "col", TableSection: "tbody", TableRow: "tr", TableCell: "td", FrameSet: "frameset", Frame: "frame", IFrame: "iframe" }); function HTMLElement() {} HTMLElement.prototype = HTMLHtmlElement.__proto__.__proto__; var HTMLDocument = document.constructor; var HTMLCollection = document.links.constructor; var HTMLOptionsCollection = document.createElement("select").options.constructor; var Text = document.createTextNode("").constructor; var Node = Text; }

That can be included into your pages to enable Safari support for HTMLElement prototyping. Alternatively, it seems that in Safari 2, a lot of these are exposed directly:

// More efficient for Safari 2 function Document() {} function Event() {} function HTMLCollection() {} function HTMLElement() {} function Node() {} Document.prototype = window["[[DOMDocument]]"]; Event.prototype = window["[[DOMEvent]]"]; HTMLCollection.prototype = window["[[HTMLCollection.prototype]]"]; HTMLElement.prototype = window["[[DOMElement.prototype]]"]; Node.prototype = window["[[DOMNode.prototype]]"];

Internet Explorer

Save the following as HTMLElement.css (and include it on your pages):

* { behavior: url(HTMLElement.htc); }

And save the following as HTMLElement.htc:

<!-- Crossbrowser HTMLElement Prototyping Copyright (C) 2005 Jason Davis, http://www.browserland.org Additional thanks to Brothercake, http://www.brothercake.com This code is licensed under the LGPL: http://www.gnu.org/licenses/lgpl.html --> <PUBLIC:COMPONENT lightWeight="true"> <SCRIPT> var constructors = { html: "Html", head: "Head", link: "Link", title: "Title", meta: "Meta", base: "Base", isindex: "IsIndex", style: "Style", body: "Body", form: "Form", select: "Select", optgroup: "OptGroup", option: "Option", input: "Input", textarea: "TextArea", button: "Button", label: "Label", fieldset: "FieldSet", legend: "Legend", ul: "UList", ol: "OList", dl: "DList", dir: "Directory", menu: "Menu", li: "LI", div: "Div", p: "Paragraph", h1: "Heading", q: "Quote", pre: "Pre", br: "BR", basefont: "BaseFont", font: "Font", hr: "HR", ins: "Mod", a: "Anchor", img: "Image", object: "Object", param: "Param", applet: "Applet", map: "Map", area: "Area", script: "Script", table: "Table", caption: "TableCaption", col: "TableCol", tbody: "TableSection", thead: "TableSection", tfoot: "TableSection", tr: "TableRow", td: "TableCell", frameset: "FrameSet", frame: "Frame", iframe: "IFrame" }; // Default initialization if (!("HTMLElement" in window)) { // Master element var control = element.document.createElement("object"); // Setup HTMLElement control.appendChild(element.document.createElement("param")); var dummy = window.HTMLElement = function() {}; dummy.prototype = control.lastChild; // Initialize other constructors for (var el in constructors) { control.appendChild(element.document.createElement("param")); dummy = window["HTML" + constructors[el] + "Element"] = function() {} dummy.prototype = control.lastChild; } // Make live element.document.all.tags("head")[0].appendChild(control); } // Constructor name var name = "HTML" + constructors[element.tagName.toLowerCase()] + "Element"; // Force inheritance for (var prop in window[name].prototype) { if (typeof element[prop] != "undefined" && prop != "dataFormatAs" && prop != "dataSrc" && prop != "dataFld") element[prop] = window[name].prototype[prop]; } for (var prop in HTMLElement.prototype) { if (typeof element[prop] != "undefined" && prop != "dataFormatAs" && prop != "dataSrc" && prop != "dataFld") element[prop] = HTMLElement.prototype[prop]; } // Map expandos to inherited HTMLElement.prototype.attachEvent("onpropertychange", function() { if (typeof window[name].prototype[event.propertyName] == "undefined") element[event.propertyName] = HTMLElement.prototype[event.propertyName]; }); window[name].prototype.attachEvent("onpropertychange", function() { element[event.propertyName] = window[name].prototype[event.propertyName]; }); </SCRIPT> </PUBLIC:COMPONENT>

This will create the constructors for all of the HTML elements (but not collections or more general interfaces such as Node or Element), and you can use them normally.