Eliminating Conditional Stylesheets - An Alternative Approach to Browser Specific CSS
Note: It's worth reading through the comments on this post, this technique isn't as useful as it first appears - Dale, Feb 18/09
Browser specific CSS is sometimes unavoidable. The common method for handling it is conditional stylesheet includes. The SitePoint article, How to Use Conditional Comments for Better CSS, which credits Paul Hammond's post, Conditional classnames, discusses a technique for doing browser specific CSS without different stylesheets. The ability to consolidate related CSS instead spreading it across multiple stylesheets seems like a great idea.
This method takes on further elegance with Drupal because the ugly conditional statements are not required to apply the classname to the body tag. The browser specific classname can be generated in the template code.
Although I'd stashed the idea away in my "cool file" I hadn't considered writing about it until I'd read Squiggy Rubio's article, A Review of Drupal 6 Starter Themes. Co-maintainer of the Basic Theme, Steve Krueger, commented that Basic uses this technique, describing it as "one 'smaller' feature that wasn’t mentioned that I think deserves its 15 minutes." Squiggy Rubio agreed, and so do I!
For Drupal 6, the implementation is simple. PHPTemplate already provides a body_classes variable via template_preprocess_page. All that's required is determining the browser and adding the appropriate classname. From template.php in the Basic theme:
<?php
function phptemplate_preprocess_page(&$vars, $hook) {
$body_classes = array($vars['body_classes']);
// Check what the user's browser is and add it as a body class
$user_agent = $_SERVER['HTTP_USER_AGENT'];
if($user_agent) {
if (strpos($user_agent, 'MSIE')) {
$body_classes[] = 'browser-ie';
} else if (strpos($user_agent, 'MSIE 6.0')) {
$body_classes[] = 'browser-ie6';
} else if (strpos($user_agent, 'MSIE 7.0')) {
$body_classes[] = 'browser-ie7';
} else if (strpos($user_agent, 'MSIE 8.0')) {
$body_classes[] = 'browser-ie8';
} else if (strpos($user_agent, 'Firefox/2')) {
$body_classes[] = 'browser-firefox2';
} else if (strpos($user_agent, 'Firefox/3')) {
$body_classes[] = 'browser-firefox3';
}else if (strpos($user_agent, 'Safari')) {
$body_classes[] = 'browser-safari';
} else if (strpos($user_agent, 'Opera')) {
$body_classes[] = 'browser-opera';
}
}
$vars['body_classes'] = implode(' ', $body_classes); // Concatenate with spaces
}
?>
Drupal 7 patch?
Comments
Doesn't work with page cache
This technique doesn't work with Drupal page caching and therefore isn't core-worthy IMHO.
Nice approach!
But what about user agent spoofing? Is it a potential problem or mainly a thing of the past?
Bevan, patches and
Bevan, patches and considerations are always welcome :) I'll definitely look into other methods and techniques that would be better suited for a similar outcome.
Thanks for the shout-out Dale!
Bevan, good point! Thanks for
Bevan, good point! Thanks for flagging that.
elv, since this isn't
elv, since this isn't security related I don't see any harm coming of it. The worse that will happen is they'll get different CSS. Am I missing something?
Steve, you're always
Steve, you're always welcome!
Note on Chrome
Interesting technique.
A side note on Chrome...
Chrome identifies itself as both "Chrome" and "Safari" and picks up the Safari styles:
Chrome/1.0.154.46 Safari/525.19
Unfortunately Chrome does not render web pages exactly the same way Safari does.
You can easily add a Chrome $body_classes variable but it must come before the Safari $body_classes variable.
For example:
} else if (strpos($user_agent, 'Chrome')) {
$body_classes[] = 'browser-chrome';
} else if (strpos($user_agent, 'Safari')) {
$body_classes[] = 'browser-safari';
}
This is a bad idea.
Those of us that have been writing web pages since before the turn of the century will recognize this approach as 'browser sniffing', something that went the way of tables for layout, animated 'under construction' gifs and the blink tag.
So many user agents can spoof the user agent string that it's an utterly unreliable method of telling what a browser actually is. Hell, there was an entire php library[1] for browser sniffing, and it was still a flawed approach. Mostly it used to be done in javascript, and was abandoned in favour of testing a browser's javascript capabilities rather than taking the user agent string at face value. That's not an approach you can take with CSS.
This may well work ok in the main browsers available today, using their default settings (assuming they all no longer pretend to be IE), but edge cases will catch you out and you can't test everything.
Conditional comments may be clunky but they do work reliably, this won't.
Some links, since you'll likely ignore me otherwise:
"The dangers of browser detects"
http://www.quirksmode.org/blog/archives/2006/08/the_dangers_of.html
"Dear Web Developers: Browser Sniffing is Stupid"
http://www.webstandards.org/2002/12/20/dear-web-developers-browser-sniffing-is-stupid/
Wikipedia (pay attention to the 'Issues and standards' paragraph).
http://en.wikipedia.org/wiki/Browser_sniffing
[1] Called phpsniff, might still be on sourceforge, google it.
After reading your comments
After reading your comments and references, I'm left, as I was before, depressed about the state of browser standards.
My conclusion is this technique is not a good idea because it won't work in an environment using caching. Which is to say, it won't work on most production sites.
Some of the other arguments don't seem worth worrying about in this specific situation, but perhaps I'm not weighting the risk appropriately. Regardless, because of caching it's a moot point.
Thanks once again everyone for taking the time to comment.