GWT ImageBundle, IE6 and AlphaImageLoader – a tale of woe

November 1st, 2009 Alex Moffat Posted in EffectiveGWT, GWT | 7 Comments »

The GWT Image Bundle is a way of improving performance when using lots of images in a GWT application. By combining multiple images into a single large image for transmission from server to client and then cutting it up on the client the number of HTTP requests the client makes is reduced. This technique is possibly more widely known as CSS Sprites but GWT automates its use. The trade off you’re making with this is that the additional time used on the client to cut apart the large image is less than the time saved by reducing the number of HTTP requests the client has to make. Unfortunately this is not always true, and for IE6 using ImageBundle can give you much worse performance than just using regular images. A recent test I ran with a list of about 600 rows, each with two images, took 28 seconds to render on IE6 with ImageBundle and 7 without. Read on to understand why this is and what you can do about it.

The problem is that on IE6 the DOM structure generated to support ImageBundle uses the AlphaImageLoader to perform the needed clipping operation. Unfortunately, AlphaImageLoader has poor performance and uses large amounts of memory. As I wrote above, my own experience confirms this. I could just go back to using individual images everywhere but on Firefox and Safari using ImageBundle does provide benefits. Instead I decided to take advantage of GWT’s deferred binding capabilities and selectively override the provided image bundle implementation on IE6 for the ImageBundles I wanted to handle differently. By doing this I can keep the same code for all browsers, for example adminImages. analyzeIcon().createImage(), but have the DOM use either a slice of single large combined image or an individual image depending on the browser.

First I created an LswImageBundle module with a module.xml file like


<module>
  <!-- Inherit ClippedImage, because IE6ImageBundleGenerator uses -->
  <!-- AbstractImagePrototype, which is in this module. -->
    <inherits name="com.google.gwt.user.ClippedImage" />
    <inherits name="com.google.gwt.dom.DOM"/>

    <generate-with class="com.lombardi.lswimagebundle.rebind.IE6ImageBundleGenerator">
        <all>
            <when-type-is class="com.lombardi.pages.admin.client.images.AdminImages"/>
            <when-property-is name="user.agent" value="ie6"/>
        </all>
    </generate-with>
</module>

so that for IE6 (and IE7) the IE6ImageBundleGenerator would be used to generate the ImageBundle for the AdminImages instead of the standard ImageBundleGenerator provided by GWT. Using when-type-is restricts IE6ImageBundleGenerator to only ImageBundles that I have control over and doesn’t let it try and generate code for general bundles used by GWT provided code.

The next consideration is the form the generated code should take. I decided to mimic the code produced by the GWT ImageBundle generator. You can see what this does by looking at the code generated for an ImageBundle, here’s part of some generated output.


  private static final ClippedImagePrototype analyzeIcon_SINGLETON =
    new ClippedImagePrototype(IMAGE_BUNDLE_URL, 887, 0, 28, 28);
  public com.google.gwt.user.client.ui.AbstractImagePrototype analyzeIcon() {
    return analyzeIcon_SINGLETON;
  }

For each image there is a static final field holding the single instance of ClippedImagePrototype used for that image. ClippedImagePrototype itself uses GWT.create() to create a browser specific subclass of the ClippedImageImpl class that it uses for the browser specific operations. I wasn’t able to just provide a new subclass of ClippedImageImpl for IE6 (to replace the existing ClippedImageImplIE6) because the image url provided to ClippedImageImpl is the single combined image file, not the individual image.

What I decided to generate was something like


  private static final UnclippedImagePrototype analyzeIcon_SINGLETON =
    new UnclippedImagePrototype("images/icons/analyze_icon_new.gif");
  public com.google.gwt.user.client.ui.AbstractImagePrototype analyzeIcon() {
    return analyzeIcon_SINGLETON;
  }

This uses a new class called UnclippedImagePrototype which extends AbstractImagePrototype, so UnclippedImagePrototype and ClippedImagePrototype are siblings in the class hierarchy. The constructor takes the url of the image to show as its single parameter. This means that the image must be served at this url, in contrast to the default ImageBundle generator that, because it constructs a single image from multiple sources at compile time, does not require that the individual images be available at runtime. Further, to make my life easier and IE6ImageBundleGenerator simpler I chose to support only the @Resource annotation for specifying the image to display and I required that the path provided to @Resource was both on the classpath at compile time (for use by ImageBundleGenerator) and available as a url at runtime (for use by the code generated by IE6ImageBundleGenerator).

The final result works very well. A saving on 21 seconds to display a single page is not to be sneezed at.

Here’s the gzipped source for UnclippedImagePrototype.java.

7 Responses to “GWT ImageBundle, IE6 and AlphaImageLoader – a tale of woe”

  1. Thomas Broyer Says:

    …and with GWT 2.0 (where ImageBundle is @Deprecated BTW) and ImageResource, it’s even easier: just add the following to your module, no need for any single line of Java code and it’ll “just work” whichever way you use the ImageResource (with an Image widget, AbstractImagePrototype, or @sprite in a CssResource)!

  2. Thomas Broyer Says:

    D’oh! I HATE when blogging systems eat what I say without prompting!

    Let’s try with ‘lt’ HTML entities:
    <set-property name=”ClientBundle.enableInlining” value=”false”>
    <when-property-is name=”user.agent” value=”ie6″ />
    </set-property>

    And actually, it’s even easier: you have *nothing* to do in GWT 2.0 with ImageResource, as it’ll use an MhtmlClientBundleGenerator instead of CSS-sprites, with no need for AlphaImageLoader (well, except if you need tranparency; see comment #6 on issue 3236 for additional information on how to use DD_belatedPNG with GWT 2.0 milestones, until issue 3588 is resolved).
    In brief: GWT 2.0 should remove all uses of AlphaImageLoader (see comment #6 on issue 3588)

  3. How long does your team plan to support IE6 for? Months, years? Decades?

  4. Now that I like. I tried the mhtml trick myself a while back but didn’t like having to generate different CSS (because of needing absolute URL to mhtml file) for production vs test. I’ll have to give the GWT 2.0 fix a try. Till then this should work out for me.

  5. Probably years. The decision is based on how many customers still have to use it. Just about anyone who can upgrade has done so already. For the rest we’ll have to wait till their machines get replaced by something that doesn’t have IE6 available and all their internal systems are upgraded to support something other than IE6.

  6. Do you mind sharing the source code for the UnclippedImagePrototype?

    Thanks,
    Ted

  7. Sure, I’ll dig it up and post it tomorrow.

Leave a Reply