Adding True Internet Explorer 9 support to Pyjamas

About two months ago, Rich Newpol added Internet Explorer 9 support to pyjamas. Before then, pyjamas tried to use old mozilla (pre-3.5 version) format whenever it detected IE9, which would result in an epic fail. One small problem, however, is that Rich’s solution was not good enough for the app I’m developing. The solution was to tell IE9 to render the website the same way previous versions of IE would render it. Since my app relies heavily on HTML5 canvas element, this means I’d be stuck with old crappy VML. Instead I have started examining pyjamas to add true IE9 support. In the end, it turned out simpler than I thought. This post talks about how I did it, as well as the quirks I had to address.

Setting up ie9 directory

To maintain at least partial support in older IE browsers, I decided to create a new __ie9__ directory inside pyjamas library. My first goal was to get pyjamas to compile a separate cache file for IE9 (*.ie9.cache.html). To do so, I had to modify home.nocache.html file inside of boilerplate to detect ie9:

...
20 else if (ua.indexOf('msie7.0') != -1) {
21 return 'ie6';
22 }
23 else if (ua.indexOf('msie 8.0') != -1) {
24 return 'ie6';
25 }
26 else if (ua.indexOf('msie 9.0') != -1) {
27 return 'ie9';
28 }
...
50 window["prop$user.agent"] = function() {
51 var v = window["provider$user.agent"]();
52 switch (v) {
53 case "ie9":
54 case "ie6":
55 case "mozilla":
56 case "oldmoz":
57 case "opera":
58 case "safari":
59 return v;
60 default:
61 parent.__pygwt_onBadProperty("%(app_name)s", "user.agent",
["ie9", "ie6", "mozilla", "oldmoz", "opera", "safari"], v);
62 throw null;
63 }
64 };
...

This tells the website to load *.ie9.cache.html if the browser identifies itself as ‘msie 9.0′. The next task is to modify pyjamas so it actually generates *.ie9.cache.html file for us. The script responsible for this is pyjs/src/pyjs/browser.py. Here are the changes I made to it:

...
21 AVAILABLE_PLATFORMS = ('IE6', 'IE9', 'Opera', 'OldMoz', 'Safari', 'Mozilla')
...
45 class BrowserLinker(linker.BaseLinker):
46
47 # parents are specified in most-specific last
48 platform_parents = {
49 'mozilla':['browser'],
50 'ie6':['browser'],
51 'ie9':['browser'],
52 'safari':['browser'],
53 'oldmoz':['browser'],
54 'opera':['browser'],
55 }
...

With these files modified, pyjamas should now generate *.ie9.cache.html file for us, that the *.nocache.html file will load if you visit the page in IE9. The problem is that there is nothing in our ie9 folder yet.

Populating ie9 directory

The obvious starting point would probably be to copy the contents of __ie6__ directory into __ie9__ and start tweaking it from there. However, after playing with that approach for a while, I started realizing that __ie9__ is so different from previous versions that the best approach is to actually leave __ie9__ blank and start troubleshooting from there. It turns out that if you leave __ie9__ directory empty, the compiled *.ie9.cache.html file is actually somewhat usable. After some testing, I found that the only methods that did not work as expected were DOM.eventGetButton(), DOM.getAbsoluteLeft(), DOM.getAbsoluteTop() (it’s possible I missed some, but my very complex web app seems to be working fine in IE9 now). After writing my own implementations of those, I noticed that DOM.py from __mozilla__ implements all 3 of those methods similar to my personal implementation, and all 3 implementations fix IE9 as well. This is not too much of a surprise, since Opera 11 now seems to run just fine with mozilla cache file as well, as I reported on pyjamas mailing list here. Perhaps __mozilla__ should be renamed to __default__ or __compliant__? Anyway, if you’ve been following along, chances are that even with all these changes you still might have issues with your web-app layout. The next section is designed to help you troubleshoot those.

Layout and appearance issues

First of all, IE9 will automatically throw you into ‘Quirks Mode’ if you don’t define a correct DOCTYPE. Quirks mode is designed to help with rendering non-W3C-compliant webpages. Its purpose is to render pages that were designed for older browsers and are no longer compliant with today’s standards. You can find more about it on Wikipedia. Actually, all browsers have a quirks mode, and they will all throw you into it if you don’t define a valid DOCTYPE. And if you look through pyjamas examples, you’ll probably notice a consistent lack of DOCTYPE. That’s right, most of pyjamas examples actually run in quirks mode when opened inside a browser (and not just in IE). This is usually not a big deal, unless you happen to be running IE9.

It turns out IE9 does not support event handler when in quirks mode. Unfortunately, event handler is one of the most important building blocks for pyjamas, as it handles just about all user interaction. So to make your web app compatible with IE9, you will have to make it W3C-compliant, this is something that will affect all browsers, not just IE9. Most of your changes will be in your app’s main html file itself and your css stylesheet, although it’s possible that you will need some slight code changes as well (in particular if you used DOM.setStyleAttribute() method).

First of all, we need to define a valid DOCTYPE. To do so, open up your app’s main html file (should be located in your app’s public directory) and add this line at the very top (before tag):

<!DOCTYPE html>

This tells it to render your app using the latest html standard (which would be HTML5). If your app looks and works great, congratulations, you know your html and can probably stop reading now. If you’re someone like me, however, you’re probably staring at a deformed version of your app while scratching your head. First of all, let’s address the issues that are common across all browsers. To do so, read my previous post about making your app HTML5-compliant. Once done, your app should appear correctly in all browsers (except maybe some glitchtes in IE9). Here are the last few fixes for the annoyances with IE9.

If you use a text area element (pyjamas’ TextArea() class), IE9 seems to override the width and height you set in the stylesheet unless you use ‘!important’ tag. It also seemed to add a scrollbar for me, so I had to set ‘overflow’ property to auto as well. I also used this oportunity to prevent Chrome from making my text area resizable (that little corner in bottom-right corner that the user can drag) by setting ‘resize’ to none:

.gwt-TextArea {
    overflow: auto;
    resize: none;
    width: 300px !important; /* important tag needed for IE9 */
    height: 100px !important; /* important tag needed for IE9 */
}

The second annoyance seems to be that unlike other browsers, IE9 (when operating in strict mode) does not assume that a single value for a field should be applied to both dimensions. This means that to center a background image, for example (as I needed to do with my loading icon), you’d need to specify “center” twice in CSS stylesheet. That should be pretty much it, I have to admit that for the most part IE9 is actually more standards compliant than Safari or Chrome. There might be other quirks, which I haven’t ran into with my app. Feel free to mention them to me so I can update this post.

Making Your Pyjamas App HTML5-compliant

I recently decided to update my pyjamas app so that it’s HTML5-compliant. I did this because I recently customized my pyjamas version to work in Internet Explorer 9 (I needed true IE9 support, not the backwards compatibility mode current version of pyjamas provides). Unfortunately, IE9 seems to be very anal about it’s rendering modes, it will either render the page in strict HTML5, or revert to half-broken ‘Quirks Mode’ that doesn’t even support event handler. It seemed easier to tweak my app to be compatible with HTML5 (besides I’d have to do so eventually anyway) than to try to write a special version of event handler for IE just so that I can run my app in a mode that’s not even meant to be used by modern websites anyway.

First of all, I had to tell the browser to render the page in HTML5 mode. To do so, I opened the main .html file for the app (in project’s public directory) and added the DOCTYPE at the very top (before <html> tag):

<!DOCTYPE html>

This tells the browser to render the page using the latest html standard (which happens to be HTML5). Second, I noticed that my fancy “loading” gif no longer appeared in the center of the page (not vertically at least). Apparently, setting my pyjamas frame to 100% height was no longer good enough. In HTML5, I now have to set both, <body> and <html> tags to take up 100% height:

html, body {
    height:100%;
}

The next issue with HTML5 that I noticed is that all my images now seemed to have a small footer below them (a few pixels of space below the image). This is probably not a big deal for most, but when you use images as icons, or need to fit exactly within a certain container, it can definitely mess up your layout. The reason for that is that HTML5 modified default display style for images to ‘inline’, which treats them the same way as text, giving them a ‘line-height’ property. There are two ways to fix the images back to normal. One way is to define ‘line-height’ for the image as 0:

.gwt-Image {
    line-height: 0;
}

This was my initial solution, but there is a problem with it. There is a bug in Firefox that overrides line-height, even if you specify ‘!important’ after it. The other alternative is to change image back to the way it was rendered before HTML5:

.gwt-Image {
    display: block;
}

This solution works in all browsers. Additionally, I noticed that the the text I placed inside tables using pyjamas’ Label() class rendered with too much spacing, as if it put every cell inside

tags. Turns out the work around for that was the same as for the images (even though the problems differ). To render the tables correctly, I modified the above code as follows:

.gwt-Table > tbody, .gwt-Image {
    display: block; /* HTML5 effects panel alignment fix, and footer removal fix for images */
}

Note that for the above fixes to work, you must not be overriding the .gwt-Image that pyjamas assigns to images, and should add .gwt-Table style to tables:

table.setStyleName('gwt-Table')

Finally, I noticed that my fonts were no longer consistent and that tables whose dimensions were defined via CSS were rendering incorrectly as well. The reason for that is that HTML5 no longer assumes that unitless numbers correspond to pixels. Which means that for all fonts, you now need to define “pt”, “px”, or “em” after the number, unless the number is zero. So something like

.text {
    font-size: 10;
}

should be changed to

.text {
    font-size: 10px;
}

The same change also applies to tables, divs, etc. If you have something as follows in your CSS:

.some-box {
    width: 200;
    height: 150;
}

you should change it to

.some-box {
    width: 200px;
    height: 150px;
}

I realize that some web designer is probably reading this now laughing at me, since I should have known to define the units to begin with. However, coming from a programming background, I see CSS more as a necessary evil, and usually stop tweaking it as soon as the page renders as expected (and up until HTML5, it rendered as expected without ‘px’ defined). Along the same lines, if you use Widget.setSize(), Widget.setWidth(), or Widget.setHeight() methods in pyjamas, make sure that you pass in the units as well (Widget.setSize('100px', '50px')) or use Widget.setPixelSize() instead. That’s all you need to do to make your pyjamas app HTML5-compliant.

To summarize:

  1. Add <!DOCTYPE html> tag at the top of the main .html file
  2. Set html and body to 100% height in CSS
  3. set images and table’s tbody to display: block
  4. Add units to every number in CSS (unless the number is 0)

There are a few IE9-specific quirks that this will not solve, but I’ll discuss them in my next post.