Data URIs, MHTML and IE7/Win7/Vista blues

December 7th, 2009. Tagged: CSS, IE, images, performance, php

2010 update:
Lo, the Web Performance Advent Calendar hath moved

Dec 7 This is the seventh in the series of performance articles as part of my 2009 performance advent calendar experiment. Stay tuned for the next articles.

UPDATE: While this post is an interesting study, the problem it solves turns out to be much simpler. The details are here. In resume: you need a closing separator and it all works fine in IE7/Vista/Win7

Let's start this post as a dialog:

- Data URIs are a way to embed the base64-encoded content of images inside HTML and CSS. Inlining images in markup or stylesheets helps you save precious HTTP requests. It's an alternative to CSS sprites.
- BUT! IE6 and IE7 don't support data URIs
- Yes, for them you can inline the images using MHTML
- BUT! Looks like IE7 on Vista and IE7 on Win7 have problems with MHTML
- [Sigh...] For those browser/OS combos there's a solution too.

I'll quickly go over what are data URIs and how MHTML addresses IE6 and IE7. If you're familiar with these, feel free to skip down to the Vista blues part.

Data URIs

Here's how to use data URIs in your pages:

  1. take an image:
    horoscopes icon image
  2. Use PHP to read this image's binary content and encode it using base64 encoding:
    $ php -r "echo base64_encode(file_get_contents('horoscopes.png'));"
    iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAADAFBMVEX///8mPnru9...
    ...more scary stuff goes here...
    dajNGGzlDAAAAABJRU5ErkJggg==
  3. To use this image in CSS, paste the encoded image content in the stylesheet following this syntax:
    .horoscopes {
        background-image: url("data:image/png;base64,iVBOR...rkJggg==");
    }

    Note: the trailing == is part of the image content it's not part of the syntax

  4. Alternatively if you want to use this image in the HTML, you can go like:
    <img src="data:image/png;base64,iVBOR...rkJggg==" />

And that's about it for data URIs, it's quite simple actually.

Data URIs in the wild

To see real-life, high-traffic sites using data URIs look no further than your favorite search engines.

Yahoo! Search using data URI in CSS for a button background:

yahoo search screenshot

Google Search using data URI in an IMG tag for a video thumb:

Google search screenshot

Base64-encoded filesizes

The base64 encoding adds about 33% to the filesize. But, then you gzip the result, the compression brings the size back to the original. Plus or minus. Interestingly enough, sometimes after base64 and gzip, the result is smaller than the original. But don't count on that.

Theoretically also when you base64 encode a bunch of images and inline them in the same CSS or HTML, you'll have a larger string to compress, so increasing the chance of repetitions and compressing better. (Todo: test this assumption)

In any event, the benefit of reducing HTTP requests will likely greatly outweigh any fluctuation in the file size.

MHTML

IE8 supports data URIs, but earlier IEs do not. For them you can use MHTML (Multipart HTML, blogged previously here)

Continuing with the previous example, in order to display the horoscopes image in IE < 8 you can use a stylesheet like this:

/*
Content-Type: multipart/related; boundary="_MY_BOUNDARY_SEPARATOR"
 
--_MY_BOUNDARY_SEPARATOR
Content-Location:horoscopes
Content-Transfer-Encoding:base64
 
iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAADAFBMVEX///8mPnru9....U5ErkJggg==
*/
 
.horoscopes{*background-image:url(mhtml:http://example.org/styles.css!horoscopes);}

A few notes on this example:

  • the image content is base64-encoded just like before
  • if you need more images, use your chosen separator prepended with --, in this case --_MY_BOUNDARY_SEPARATOR
  • Content-Location:horoscopes is how you give a name to the image in order to use it later
  • then later in your stylesheet you can reference the name in the stylesheet using http://url/styles.css!horoscopes
  • you need absolute URLs in the mhtml:http://.... part (that's retarded, hope I'm mistaken. Didn't work for me with relative URLs)
  • you can use a single CSS file to support both old IE as well as new browsers, but you'll have to repeat the image stream twice, probably a better approach is to use browser specific stylesheets

For more examples of MHTML (which is also used for multipart email messages) check this and for a brilliant example of using one MHTML to embed images in HTML (not CSS), check Hedger's test page here (appears 404 at the moment of writing). For a tool to automate the dataURI-zation/MHTML-ization, be sure to check Nicholas Zakas' CSSEmbed

The problem with IE7 on Vista (and Win7)

So far we have data URIs and MHTML fallback. Turns out we're not done yet, because IE7 on Vista and Windows 7 fails with the MHTML example. Two points:

  • IE8 is the default in Windows 7, but it comes with several IE7 modes (either by default or turned on by users when their favorite pages break). Compatibility view, ie7 mode, ie8 in ie7 mode, blah-blah, it's all pretty confusing, but all modes with the exception of IE8 in proper IE8 standards don't work with the MHTML
  • I tested Windows 7 evaluation copy, but I have the bad, bad feeling that Windows 7 normal copy will be as broken when it comes to MHTML. I believe it has something to do with security, if anyone finds an article on MSDN, or anywhere, please share.

The solution to the Vista problem is to split the CSS shown above (the one that uses MHTML) in two: one which contains the encoded images (the commented part) and a second one which only has the normal CSS selectors part.

In other words have something like:

  1. mhtml.txt:
    Content-Type: multipart/related; boundary="_MY_BOUNDARY_SEPARATOR"
    
    --_MY_BOUNDARY_SEPARATOR
    Content-Location:horoscopes
    Content-Transfer-Encoding:base64
    
    iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAADAFBMVEX///8mPnru9....U5ErkJggg==
    

    No need to use comments. Could be .txt, .css or anything you like. In my tests the content-type didn't matter - text/html, text/css, text/plain all worked

  2. styles.css:
    .horoscopes{*background-image:url(mhtml:http://example.org/mhtml.txt!horoscopes);}

    The normal CSS simply references the new MHTML file appending !identifier

  3. in your HTML you don't need to reference the MHTML document, you just point your link tag to styles.css as usual and styles.css contains the reference to the MHTML doc

Time to celebrate? Almost.

Another ugly bug surfaces - this procedure outlined above works only once. Once the MHTML.txt is cached, it stops working. That means repeating visits to the page or other pages using the same mhtml. The effect also means hovering on the same page. Say you have two images - one normal and one mouse over, both in the same MHTML. The normal works and the hover breaks, mouseout breaks too. Insane, isn't it?

There's a solution though - you should make the browser request the MHTML document every time. This is kind of backwards when it comes to performance optimization, where we want to cache everything. It's pretty unfortunate. The best you can do is avoid sending the MHTML every time, but only send a "not-modified" header. In order for this to work, the browser has to send If-Modified-Since or If-None-Match.

So you can send your MHTML file with an ETag (yes, ETags can help sometimes ;) ) Then the browser will send an If-None-Match and you can happily reply "304 Not Modified".

Sounds complicated? It sure is. And, remember, all this is only for IE7 (or any IE7 mode) on Vista and Windows 7. All other IEs on all other platforms work with the easy MHTML and IE8 works with data URIs.

Everything is a trade-off (see last night's post) and I can understand how you may think "That is one big tradeoff". There's always the option of serving normal images to the affected browser/os and just don't implement this optimization for them.

But it's good to know there is a solution.

DataSprites class

Let me offer this DataSprites PHP class I coded that takes care of all the scenarios. It takes a bunch of image filenames as input and produces:

  • a CSS containing data URIs for normal browsers (example output)
  • an MTHML CSS fallback for IE6,7 (example output)
  • a CSS that refers to an additional MHTML for IE7 on Vista, Win7 (example output - css and mhtml). The MHTML in this case is sent out with an ETag (derived from the input image filenames) and consecutive requests with the same ETag return 304 Not Modified

You can see a test page here and view the source code here. The directory listing is also available if you want to grab the images for testing.

The perfect use case for such an approach is when you want to combine background images on the fly. When you would normally use CSS sprites, but you want to produce them dynamically at run time (maybe because you have too many combinations?).

I've tested this in Safari, Firefox, Opera, IE6, IE7 and IE8 (in all compat modes) on Vista and Windows 7 evaluation copy. If you find the test page is not working for you, please let me know.

In this DataSprites class, I'm checking the problem OS looking for the strings "Windows NT 6" and "Windows NT 7" in the user agent string. My Win7 evaluation copy had "Windows NT 6.1" in the UA, so I may be a little forwards-aggressive with the check, but somehow I thought Win7 proper should have "Windows NT 7" in the UA string. And also that Win7 will suffer from the same issue as Vista and Windows 7 evaluation.

UPDATE: Added a hover example.

UPDATE 2: Recorded a screencast to demo the IE7/Vista experience

Thanks!

Thanks for reading! Now you know all about data URIs, MHTML, and maybe a little too much about IE7/Vista's challenges :) Ready to use data URIs and save some HTTP requests?

Tell your friends about this post: Facebook, Twitter, Google+

29 Responses

  1. Sorry, but MHTML for IE7@Vista is still not works. I tested your page and also made hover test http://fullajax.ru/temp/datauri/phpied/test.html.

    I think, there is no fully working solution on basis data:uri (mhtml) sprites for IE7@Vista. Unfortunaly, for IE7@Vista we need use normal standard images or standard css sprites.

  2. Thanks Ruslan!

    I don’t have Vista handy to test your example, but are you saying that my test page doesn’t work for you either?

  3. Yes, in your test page images displays only on first loading, after reload displays no images.

  4. I have IE7@Vista and can confrm Ruslan’s findings. The test page he links to works fine in Chrome. However the first time you view it with IE7 you will see the AOL icon in the Test Hover box. When you mouse over the AOL icon disappears. It does not come back when you mouse out of the element. On repeat views of the page no graphics at all appear in the Test Hover element.

  5. hm, that’s exactly the result I was seeing before adding the ETag to cause the browser to make conditional requests. Maybe I broke something there

    do you guys see the conditional get request going out (with 304 response) to the mhtml doc datasprites.php?extra=1

  6. ok, got it, it’s the future Expires header I added that was causing the browser not to send a conditional get

    fixed now – ie7/vista gets expires yesterday so they can send If-None-Match and the server returns 304

    all others get future expires

    added a hover test page:
    http://phpied.com/files/datasprites/testhover.html

    As you can see, not the best user experience, because there’s a quick flash while the conditional get returns 304 in IE7/Vista. So probably if the page has many hovers, best to stick with the normal sprites or individual images, otherwise data sprites are a viable (albeit complicated) solution.

  7. and a video to illustrate ie8 vs ie7/vista – http://www.youtube.com/watch?v=xEXY15fV47Q

  8. @Stoyan, thanks fo video, very clearly.

    I think, re-requests of mhtml css it is anti-optimization, browser always request large mhtml file.

  9. Yep, Ruslan, it’s unfortunate. At least with the 304 response, we don’t have to send the mhtml every time, but it’s still a request, even if for a zero-size body.

  10. Windows 7 == Windows NT 6.1. It’s only a minor update to Vista.

  11. We’ve been using this technique for a while and simply setting background images in the standard way for IE7 on Vista. However, we have run into a problem using mhtml with https. If the site is using https we get the “This webpage contains both secure and non-secure content” warning. Have you seen this before, and have you found a workaround?

  12. fixed bug ie7/vista

    /*
    Content-Type: multipart/related; boundary="_MY_BOUNDARY_SEPARATOR"
    --_MY_BOUNDARY_SEPARATOR
    Content-Location:horoscopes
    Content-Transfer-Encoding:base64

    iVBORw0KGgoAAA....U5ErkJggg==

    --_MY_BOUNDARY_SEPARATOR--
    */

    via rfc2557 page7

  13. aoao,
    I did not understand your answer – Which bug did you solve and how?

    For me, in rare cases, MHTML under IE8/Win7 in emulate-IE7 mode still doesn’t work well

  14. Hi!

    Finally I found a Temporary solution with IE7 on Vista, it was a headache , a nightmare.

    Thank You!

    P.S. @Prestaul very strange this should be appear with SSL certificate probles, I suggest to make a checkup!

  15. Excellent post, can I translate it to Chinese and paste it to my blog?

  16. [...] by a comment at a previous post all we ever needed was to close the boundary delimiter and add two dashes at the end. The [...]

  17. Just found out the hard way something that should be pretty obvious.

    If your site happens to have a meta http-equiv for X-UA-Compatible and the content attribute is set to IE=EmulateIE7 , this causes much dataURI pain for IE8. haha

    After I set it to IE=8, all was good. :)

  18. [...] Od dość dawna przeglądarki wspierają modyfikację taga “img” pozwalającego na wstawienie zamiast linka do pliku graficznego- jego postaci zakodowanej wg algorytmu base64 (dla przeglądarek IE 7 i starszych które mają z tym różne problemy są opisane metody “na około”, np. tu). [...]

  19. [...] 其实在测试页面我已经使用data URI内嵌图片给这个小图片保存整个HTTP请求。 [...]

  20. What tricks can I use to make images on my site appear to load faster?…

    I see you have got a very good set of answers covering all areas, here’s one more. Data URIs can really help your site cut down on HTTP requests required to load a page. Read this post by Marcel on different approaches we took at Yahoo! to optimize im…

  21. free windows 7 themes…

    [...]Data URIs, MHTML and IE7/Win7/Vista blues / Stoyan’s phpied.com[...]…

  22. filmy z napisami…

    [...]Data URIs, MHTML and IE7/Win7/Vista blues / Stoyan’s phpied.com[...]…

  23. [...] 其实在测试页面我已经使用data URI内嵌图片给这个小图片保存整个HTTP请求。 [...]

  24. i don’t understand the mhtml stuff, but i avoid ie.
    however, both the sprite and base64 are loaded just once in the css file, right?
    so there’s little difference
    perhaps you could edit the base64 easier when you decided to delete one of the icons versus editing the sprite image file to remove the obsolete icon.
    i suspect base64 method requires css, rather than inline in html, because duplicate base64 strings can’t be cached (i assume)

  25. Heya admin, I just wanted to give you a quick heads
    up that your Website url: http://www.phpied.

    com/data-uris-mhtml-ie7-win7-vista-blues/ is being flagged as a potentially harmful web site in my browser chrome.
    I would highly recommend having someone look into it. You
    could certainly lose a lot of site visitors due to this problem.

    Very best of Luck.

  26. [...] 其实在测试页面我已经使用data URI内嵌图片给这个小图片保存整个HTTP请求。 [...]

  27. I think that is among the most significant info for me. And i’m happy reading your article. However should statement on some normal things, The website style is ideal, the articles is in point of fact great : D. Good activity, cheers

  28. “i don’t understand the mhtml stuff, but i avoid ie.” Let ie die naturally!

  29. This can be a set of words, not aan essay. you might bee incompetent

Leave a Reply