Sunday, July 09, 2006
IE Trident Easter Eggs
I created the Trident easter eggs in IE 4 and IE5.
The IE 4 one is a sinusoidal animation of the names of the people who worked on Trident. I used the letter spacing property to perform
the animation, and when the names collapse, I replace the name with the next name. I think it is a pretty cool effect. If you click once on the "<TRIDENT>" tags and the center name, I made my name display instead of cycling through the other names. An Easter egg in side an
Easter egg :-)
The IE 5 one
(fixed)
is a gravitational simulation where the names of the people who worked on the product are the gravity wells and a collection of colored dots orbit the names. I had to play around with how much friction to simulate, as early on, the dots tended to fly away to infinity! Again, I hid my own Easter egg in side the Easter egg. In the top left part of the window are some NBSP's where the cursor will turn from an arrow to a caret. Clicking on this will cause only my name to appear.
After IE 5.0, the pointy haired managers at Microsoft decided that Easter eggs comprised a security thread (or at least they thought corporate customers had this thought) and eggs were removed from the product. Turds.
Speaking of Easter eggs. The project that turned into Trident, called Forms^3 (Forms cubed) also has an Easter egg in which you can find my name. This one was created by Terry Lucas. Forms^3 is a forms editing and runtime package which exists in the Microsoft Office products. It exists in Excel 2002, but I'm not sure if it is in later versions. Perhaps someone can check them out.
You get to the Forms^3 Easter egg by starting the Visual Basic for Applications tool.
- From the "Tools" menu, click on the "Macro" sub menu and then on the "Visual Basic Editor" item. This is usually bound to "Alt-F11".
- Then, in the editor, click on the "Insert" menu and insert a new "UserForm". This brings up a canvas onto which you can drop controls.
- Create a list box by dragging the listbox icon from the toolbar onto the canvas.
- Then, modify the "RowSource" property of the list box to have the value "Alchemy_Forms^3". The list box will now be populated with the first names of all the people on the Forms^3 team.
- You can then set the "ColumnCount" to be 3 to see the last names and the role each individual had on the team: dev, test, etc. You may need to make the list box larger to show this additional information.
- You can activate the control by clicking on it at which point you will be able to scroll through all the names.
Sunday, July 02, 2006
Code Karma
I've recently been writing client side Java Script for an HTML user interface I've been building at work, and I ran into an issue with Internet Explorer which I was at least partially responsible for 10 years ago! Let me explain what it is, why it is and an effective way to work around it.
I was attempting to dynamically replace a number of rows in a table with a different set of rows. I was using the innerHTML property of an element which takes a string, parses it as HTML and replaces the contents of that element with the new HTML. In this case, I try to replace the contents of a TBODY with a new set of rows:
<table>
<tbody id="rows">
<tr>....
<tr>....
...
</tbody>
</table>
var tb = document.getElementsByName('rows')[0];
tb.innerHTML = "<tr>......";
This works just fine in Firefox. The new rows replace the old and the display updates. However, in Internet Explorer, one gets a script error stating that there was a runtime error!
At first, I thought that IE was not capable of performing the redraw for modified tables with innerHTML, but then I remembered that I was responsible for this limitation! How many developers get to deal with the consequences of their decisions about products at a later date? Probably not many. Let me describe how this came to be.
About 10 years ago, I was part of the Trident team. Trident was responsible for implementing the parsing, rendering and object model for the next version of IE, after 3.0. Also known as mshtml.dll. I was a developer responsible for the in-memory representation of the HTML and the dynamic manipulation of that HTML.
One of the things I did during this time was invent the method innerHTML, along with innerText, outerHTML, outerText and the lesser known insertAdjacentHTML and insertAdjacentText methods. These were methods which took HTML or raw text and replaced/inserted that new content into the document.
Now, Microsoft documents this as not applicable to table elements. Why? They don't say. However, I remembered why.
When one sets the innerHTML property of an element, the string containing the HTML is run through the parser. Now, HTML parsers are not simple, straightforward parsers like XML parsers. The HTML parser (implemented brilliantly by David Bau) takes arbitrary text and, usually, produces an HTML tree of elements. For example, parsing a file containing only "Foo" will result in the tree:
<HTML><HEAD></HEAD>
<BODY>Foo</BODY></HTML>
You can see this for yourself by running the following through IE (Firefox won't work, as they did not implement outerHTML):
Foo<script>alert(document.body.parentNode.outerHTML)</script>
Now, parsing something like "<tr><td>Foo" where there is no TABLE tag preceding the TR causes the parser to ignore the TR tag altogether. This was probably done by the IE parser for backwards compatibility with the Netscape browser of the time. In fact, much of the complexity of the parser is influenced by backwards compatibility.
So, attempting to set the innerHTML of a TBODY with "<tr>..." would result in setting the contents of the TBODY with "Foo". This is not terribly "valid" or displayable HTML. In order to get that TR created, you need to precede it with a TABLE tag. However, attempting to set the contents of the TBODY with "<table><tr>..." makes even less sense because injecting a TABLE directly in a TBODY is also meaningless.
What this all calls for is what I used to call "Contextual HTML Parsing". This is a mode of parsing where a branch of an existing HTML tree was to "seed" the parser with a context with which the parser would then interpret a string to parse. Thus, if the branch of tags were (from the bottom) TBODY, TABLE, BODY, HTML and one were to parse "<tr>...", the "Contextual HTML Parser" would know that creating a TR was okay because its immediate parent would implicitly be a TBODY, a valid container for a TR.
Nifty concept, this contextual parsing. The problem was that we never had enough time to implement such a feature. And, in order to deal with attempts to modify tables in such a manner, I prohibited the modification of tables with innerHTML and other methods.
An alternative to all this would have been to "hack" something up. For example, I could have checked to see if the innerHTML of a TBODY was being set to something which began with a "<tr>". Under these circumstances I could have prepended a "<table>" to the string, and then plucked the TR's out of the resulting tree and replaced the contents of the TBODY with them.
Sounds simple enough until you have to consider all the variations. Like, what if the string to be parsed looks like "<!-- new rows --><tr>...". Pretty soon you start doing all the work the real parser has to do.
So instead of hacking up something very incomplete and possibly erroneous in many cases, I left the modifications of tables with innerHTML out of the product. It would have been fun to modify the parser to deal with non textual context!
I wonder how Firefox implemented this. Perhaps I'll find the time to look at the code sometime....
The workaround for this is actually not all that bad. What I did was to insert a SPAN tag into my original page with the visibility style set to hidden. When I wanted to replace the rows, I would set the innerHTML of this span with something like "<table><tbody><tr>...". Because the span is not visible, this does not cause the page to redraw. Then, I would use the DOM method replaceChild of elements to remove the old TBODY and replace it with the newly parsed TBODY. This resulted in the table changing and being redrawn correctly!
<table>
<tbody id="tb">
<tr>....
<tr>....
...
</tbody>
</table>
<span id=temp style='visibility:hidden'></span>
var temp = document.getElementsByName('temp')[0];
temp.innerHTML = '<table><tbody><tr><td>New Row';
var tb = document.getElementsByName('tb')[0];
tb.parentNode.replaceChild(temp.firstChild.firstChild, tb);
You can see this in action here.