Customizing CSS
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:
-
We could do a complete redesign, and move the menu to the less controversial left side. This would be in accordance with several design principles often quoted on the web. However, tests indicated to us that users of visual browsing systems often preferred content to start in the upper left hand corner of their browser. 1
-
We could follow the philosophical route, and accept graceful transformation of the visual presentation. This would, however, mean a redesign pushing the content to the right, as well as yet again placing the menu on the left against better judgment.
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 Wmake 5. 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:
<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:
<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:
[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:
[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:
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:
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:
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.
5 Wmake is scheduled for open source release later this year. A notification will be posted at the appropriate time.