Customizing CSS

This document detail the work done by Tina Holmboe of Greytower Technologies to build a CSS delivery system using browser–sniffing, and explain why it was ultimately given up on.

Tina Holmboe

Background

When we first created our multi–national website in 2002, the decision was made to use CSS 2 position : fixed ; in order to enable the main menu to stay fixed to the view–port regardless of document length.

This introduced a problem since the rather popular browser Internet Explorer does not support this positioning scheme in its Microsoft Windows version, and defaults to static as is the initial value per the CSS specification.

This has the undesirable consequence that the menu — usually positioned on the right hand side and fixed — falls to the left, and up, thereby obscuring the start of the content.

A number of solutions now presented themselves:

At this point in time we decided to indulge ourselves in an experiment. The site already implements content negotiation based on the Accept–Language HTTP header, and this section from the HTTP 1.1 specification inspired us further:

The User–Agent request–header field contains information about the user agent originating the request. This is for statistical purposes, the tracing of protocol violations, and automated recognition of user agents for the sake of tailoring responses to avoid particular user agent limitations.

Postulating that different levels of CSS support in browsers is, indeed, a legitimate target for content negotiation through the User–Agent field we set out to construct a solution to do just that.

Obstacles

The User–Agent field may be the single most misused part of the HTTP specification. In the past it has been a favorite way for some sites to block or hamper certain browsers in order to deny their users access to content. Masquerading — presenting themselves as something they are not — has become a common trick for many browsers. This was the first difficulty to overcome.

It was natural to first examine the problem in detail. Analysing server logs from a well visited site found an approximate 1,700 (+/- 10%) unique user agents. 2. It quickly became clear that for a majority of these, we had no information what so ever upon which to judge their capabilities. 3

This clearly suggested that sending a CSS file tailored toward a specific user agent would be highly inadvisable. In addition, such an approach would not be future–proof, ie. a new browser would have to be analyzed and a CSS file tailored to its capabilities added to the system.

Reversed Targeting

We decided to reverse the method: a 'master' CSS file was created that held the visual layout as perfected using a reference browser 4. This file will be served by default to any user agent requesting a stylesheet. Specific CSS files will only be served to clearly identified user agents with issues regarding support for CSS.

This method will ensure that future browsers must be added to our list of exceptions if — and only if — they are identified as having difficulty with CSS support.

Static vs. Dynamic

The principle decided, we turned our attention to the details of implementation. We wanted to avoid generating each page dynamically, as the entire site consists of static pages only, generated offline using the pre–processing tool Wmake5. We have no real need at this time for pages created on–the–fly.

With this in mind we sought a method of serving the CSS file dynamically, whilst leaving the markup code static. A user–agent supporting CSS will, in most cases, also support the following:

Example

     <link rel="stylesheet"
            type="text/css"
             title="Styles"
              media="screen"
               href="/path/to/file.css" />
    

and since this construct instructs the user–agent to request the named resource from the server, our problem was solved. Every static page on Greytower.net now contains the following code:

Example

    <link rel="stylesheet"
           type="text/css"
            title="Greytower: Default Screen Layout"
             media="screen"
              href="/cgi-bin/getResource.cgi" />
    

The href attribute to this link tag indicates to the browser which resource to request as the source for the CSS data needed. What that resource is means nothing to a browser, as long as the correct type of data — in this case CSS — is served.

The getResource.cgi script doesn't really return CSS data, but a HTTP status code, specifically 301 Moved Permanently which, alongside Vary: user–agent should instruct the browser to request the CSS file from the specified location the next time around, unless its User–Agent identification changes.

Reducing Maintenance

The effort needed to properly maintain the site was expected to grow substantially with each needed branch of the CSS file. Time, as always, is costly and we sought to find a method that would allow us to keep one single file from which several branches could be automatically produced.

The logical extension of this thought was to add some sort of code to a master CSS file, and create a parser which — when seeing these codes — could duplicate the file with the proper, changed, values.

We contemplated the use of a tiny XML–based language, but decided against it for its verbosity. We settled on the following format:

Example

     [replace: CSS-PROPERTY-NAME
	default = CSS-PROPERTY-VALUE
	id1     = CSS-PROPERTY-VALUE
	id2     = CSS-PROPERTY-VALUE
	 .
	 .
	 .
	idN     = CSS-PROPERTY-VALUE]
     

to be used inside a CSS rule set. In addition, the following block is placed at the beginning of the master file:

Example

     [versions
	sourcepath = /path/to/source/tree
	targetpath = /path/to/target/tree
	default    = name-of-default-file.css
	id1        = file1.css
	id2        = file2.css
	 .
	 .
	 .
	idN        = fileN.css]
     

The ID as specified in the versions block correspond to the one used in the replace block above. The parser will create one copy of the master file per ID. For each copy the replace blocks will be substituted with the specified CSS-PROPERTY-NAME set to the CSS-PROPERTY-VALUE matching the ID. If none of them do, the default value is used.

This allows us to easily do such things as this example from our master CSS file illustrate:

Example

     DIV#mainMenu {
      z-index                : 2 ;
      [replace: position
            default = fixed
            windows-msie-4 = absolute
            windows-msie-5 = absolute
            windows-msie-6 = absolute]
      [replace: left
            windows-msie-4 = 89%]
      top                    : 72px ;
      right                  : 2px ;
      width                  : 95px ;
      height                 : 266px ;
      visibility             : visible ;
      [replace: float
            linux-netscape-4   = left
            windows-netscape-4 = left
            windows-opera-3    = left]
      [replace: margin-left
            windows-opera-3 = 1em]
     }
    

The Tools

In order to make this work we needed some very specialized tools: a system for parsing — correctly — the HTTP User–Agent field, a program to mimic a resource and return the correct CSS data, and a parser with which to separate the master CSS file into UA dependent sections.

PetiteCGI

As part of another project, Greytower have for some time been involved in developing a CGI library package implemented in Perl. Part of this package is the Agent.pm library, which has one and only one task: guessing the correct identification of user agents based on the User–Agent header.

Currently Agent.pm consist of 1,311 (thirteen hundred, yes) lines but does a fair job of guessing right by use of massive heuristics. The source code is available for download should you be inclined to have a look at it.

getResource.cgi

We then proceeded to create the getResource.cgi program. It is built using Perl, and is currently at 141 lines. A copy of getResource.cgi is available for download. It is also scheduled for a complete rewrite including the added capability of using an external configuration file.

cssMake.pl5

The final building–block is the cssMake.pl5 Perl script which parses the CSS master and produces the browser specific files. This can most easily be described as a 'hack', and needs a thorough rewrite. You can download a copy of the current version.

The CSS

For those particularly interested, we have included the original master CSS file from which all the Greytower UA–specific stylesheets are built. This file is specific for media type 'screen'.

Now what ?

A good question indeed. A this point in the narrative we have established what we want to do, and how we want to do it. However, one very important point remain: with the "ability" in place to arbitrarily change one, or more, CSS property/value pairs on a per–browser basis, which changes should we make ?

Taking yet again the conservative approach, we decided to make only such changes as prevented the overall layout from collapsing. The original reason for this exercise is a prime example:

Internet Explorer for Windows do support CSS to a large degree, including the absolute positioning scheme. It does not support the fixed scheme — but when encountering it defaults to static.

This would seem to be a sensible fall–back, as second–guessing the author by defaulting to absolute might have undesirable effects.

Since much of our layout is based around fixed positioning, the following can be found in our master CSS file:

Example

   DIV#mainMenu {
    z-index                : 2 ;
    [replace: position
          default = fixed
          windows-msie-4 = absolute
          windows-msie-5 = absolute
          windows-msie-6 = absolute]
    /* some code snipped */
   }
    

This will ensure that the three specific CSS files served to the Windows versions of IE 4, 5, and 6 uses the absolute positioning scheme, and not fixed. This might achieve a reasonable faximile of the visual layout desired.

Conclusion

Was it worth it ?

The truth is that the ability to make minor visual corrections to the way a website (theoretically) renders is very seductive. The desire to have this ability is perhaps the single most destructive emotional response on the Web — mudding the waters, and taking focus away from the all important structure which forms the backbone of information exchange.

This experiment has shown us that it is possible, with good planning and tools, to correct for known bugs in browser support for CSS. Even more importantly, it is a method which completely separates the content, and the structure of the content, from the ability to do detailed manipulation of rendering across browsers.

Our last example uses the newsBubble from Greytower.net's first page. It is structured as a DIV containing in turn one header level two (H2), one SPAN that holds a date, and one P for the actual news information.

Entirely separated from the structure is the, by CSS suggested, rendering. We elected to use the CSS max–width property in an attempt to restrain our newsBubble from stretching beyond a maximum width of ten times an EM, roughly speaking 10 characters wide. We would prefer not setting an explicit width on the content, as that would somewhat impair a user's ability to re–size the window of a GUI–based browser.

This property, however, is not supported by several versions of the Konqueror series browser. In these, the lack of an explicitly specified width means that the newsBubble spans a hundred percent of the parent element — ie. the #Canvas. Since the newsBubble is also floated to the left, the content will be rendered outside the canvas area on the right hand side, under the menu.

To avoid this, you can find the following rule set for the newsBubble in our master CSS file:

Example

   DIV.newsBubble {
    color                  : rgb(0, 0, 0) ;
    background-color       : rgb(214,222,222) ;
    min-width              : 4em ;
    max-width              : 10em ;
    [replace: width
          unknown-konqueror-2.0.1 = 10em
          unknown-konqueror-2.1.1 = 10em
          linux-konqueror-2.2.11  = 10em
          windows-msie-4          = 10em
          windows-msie-5          = 10em
          windows-msie-6          = 10em]
    float                  : left ;
    margin-left            : 1em ;
    margin-top             : 1.5em ;
    margin-right           : 0.5em ;
    margin-bottom          : 1em ;
    padding                : 0.5em ;
    border-right-style     : solid ;
    border-right-color     : rgb(60, 179, 113) ;
    border-right-width     : 1px ;
    border-bottom-style    : solid ;
    border-bottom-color    : rgb(60, 179, 113) ;
    border-bottom-width    : 1px ;
    text-align             : center ;
   }
  

This, when parsed, means that three versions of Konqueror in which we have identified the bug/lack of support, will receive a CSS file with the width property explicitly set. The end result is that for users of Konqueror, the visual rendering of Greytower's first page will not turn to mush, yet for users of more CSS capable browsers the more flexible max–width property will allow for a greater range of re–size capability.

So, to quote J and K:

Hey … is it worth it ?

Yeah. Yeah, it's worth it.

Post–fact Analysis

After running with the above mentioned method for customizing stylesheet responses for over a year, we decided to terminate the experiment. This was partially due to a desire to redesign the site for even better accessibility, but sprung also from a wish to avoid the per–request scripts that was needed.

As of version 5.0.0 of the website, we are back to using static stylesheets.

Notes

1 This seemed — logically enough — particularly important to users employing screen magnification systems overlaid on normal, often CSS supporting, browsers. When a visual browser window is magnified to some 5–600%, it is important that the user need not hunt for the content through a sea of eye–candy.

2 A total of 977,392 requests with User–Agent details were analyzed.

3 Such agents as Ariadna, Katipo, WebBandit and NetAttache were, and is, unknown to us.

4 The reference browser used is — currently — Mozilla 1.0rc1, which can be found at the Mozilla Project Homepage. This browser is preferred to later release candidates for its continued support for the LINK elements.

5Wmake is scheduled for open source release later this year. A notification will be posted at the appropriate time.