Archive for July, 2025

Incompetent Browsers: towards a complete, modern solution, part I

Wednesday, July 30th, 2025

In Part One, we looked at the historical practice of browser grading. In Part Two, we looked at some historical solutions for browser detection. Today, we’re going to start putting it together.

Requirements

Let’s start by thinking about browser support. A lot of our historical solutions came from a world where ongoing support for IE was an issue; thank goodness we don’t live there anymore. Our biggest danger these days is someone coming to our site with an old phone that has a several years old version of webkit or chromium. Especially if they are using an app-hosted browser that uses a system webview.

There are 3 browser engines commonly in use, and 3 obsolete browser engines. These are our browser grades:

A-grade experience: If someone comes to the site with an up-to-date browser running Gecko, Blink, or Webkit, we want to give them the full experience.

Obsolete C-grade experience: If someone comes to the site with an out-of-date browser running Gecko, Blink, or Webkit, we want to give them the plain HTML experience.

Archaic C-grade experience: If someone comes to the site with a browser running Trident (IE,) Spartan (Old Edge,) Presto (old Opera,) or some other ancient thing (Netscape? iCab? Lynx?) we want to give them the plain HTML experience.

It’s probably worth the time to add a few more functional requirements:

Opt-In: We’ll have to develop a plain HTML experience for our site. It would be nice to be able to develop this in a modern browser, so we should be able to opt-in to the plain HTML experience for development.

ES3/Jscript Compatible: If we write any code that decides if we should load our JavaScript and CSS, that code will have to run successfully in any browser. Not just any modern browser; any browser. This means limiting ourselves to EcmaScript version 3, and also to the limitations of the not-quite compatible JScript. This code will be written like it’s 1999.

Let’s write some code

The simplest answer is to load our full experience with a module tag, and let it load everything.


<script type="module" src="./main.js"></script>

And maybe you can get away with that. But this is likely to leave you with a Flash of Unstyled Content, as your CSS won’t be loaded until your JavaScript runs.

We really want something like this:


if (browserIsGood) {
  loadJavascriptAndCSS();
}

Okay, that’s a simple structure, but there’s a lot of handwaving happening there. Let’s dig in.

Loading JavaScript

Can we just use dynamic import?


if (browserIsGood) {
  import("./main.js");
}

No, we can not! import is a reserved keyword in JavaScript. This will crash if you try to run it in a browser that doesn’t support dynamic import. We’ll have to load it the old fashioned way.

Everyone knows how to do this by now, right?


var target = document.getElementsByTagName("script")[0];
var head = target.parentNode;

function insertScript(src) {
  var s = document.createElement("script");
  s.setAttribute("src", src);
  s.setAttribute("type", "module");
  head.insertBefore(s, target);
}

if (browserIsGood) {
  insertScript("./main.js");
}

Here we use the first script tag as the target for inserting, because the page might not have a head tag, and we create a script tag dynamically. Our JavaScript will be loaded asynchronously, so we’ll need to wait for DOMContentLoaded before we do anything. But these are table stakes for a modern web site these days. No big deal.

Loading CSS

So how can we load CSS dynamically without causing a FOUC?


var target = document.getElementsByTagName("script")[0];
var head = target.parentNode;

function insertStyle(src) {
  document.write('');
}

if (browserIsGood) {
  insertStyle("./main.css");
}

Wait, really? document.write? Yes. I’m sorry, but if you want to prevent a FOUC, you need there to be a link tag in the head before the parser sees the body. Browsers will block rendering in that case, and will not block rendering, leading to a FOUC in every other case.

Unless you want to get a little crazy and do it all yourself.


var target = document.getElementsByTagName("script")[0];
var head = target.parentNode;

var hide = document.createElement("style");

function hideDocument() {
  hide.innerText = "body {display:none;}";
  head.insertBefore(hide, target);
}

function handler() {
  head.removeChild(hide);
}

function insertStyle(src) {
  var ss = document.createElement("link");
  ss.setAttribute("rel", "stylesheet");
  ss.setAttribute("href", src);

  if (ss.addEventListener) {
    ss.addEventListener("load", handler, false);
    ss.addEventListener("error", handler, false);
    waiting[ss.href] = true;
  }

  head.insertBefore(ss, target);
}

if (browserIsGood) {
  hideDocument();
  insertStyle("./main.css");
}

Here we add a style tag that hides the body tag, and then we remove it once the stylesheet loads (or fails to load). Note that we only call hideDocument once we know we’re inside a good browser.


Incompetent Browsers: Browser Detection

Tuesday, July 29th, 2025

Browser Grading is all well and good, but eventually you have to actually do something different in the browsers you want to get the full experience and the browsers you want to have a simplified experience. Today, we’ll blitz through a bunch of techniques that people have used in the past to do this.

Ultimately, all of these techniques are about Graceful Degradation. There’s a lot of discussion out there about Graceful Degradation and its politer cousin Progressive Enhancement. But to handle truly incompetent browsers, we basically want to turn off all the fancy stuff. Generally speaking, this means you want to load your JavaScript and CSS only if the browser is good enough to use it.

IE Conditional Comments

Good ol’ Internet Explorer has a weird feature called conditional comments that lets you write HTML that only shows in IE, or only shows in non-IE browsers. Of course, when they made IE slightly better, they turned it off so that they would get the good code, but IE 5-9 can be excluded this way.


<!--[if !IE]> -->
According to the conditional comment this is not IE 5-9
<!-- <![endif]-->

CSS Hacks

By taking advantage of known CSS parsing limitations, you can write CSS that will only be read by certain browsers. Be careful that you don’t end up writing two different stylesheets in one file though.

This famous CSS hack sends different widths to older IE browsers and other browsers, which was super important when IE improperly implemented the CSS Box model.


div.content { 
  width:400px; 
  voice-family: "\"}\""; 
  voice-family:inherit;
  width:300px;
} 

Test everything

Instead of checking for browsers, what it we checked for features! Several JavaScript libraries made this convenient, with has.js and Modernizr being the hottest ones back in the day. Modernizr is even still being maintained!

Eventually, the browser rolled these ideas into real features, with the @supports rule, and CSS.supports.


result = CSS.supports("display: flex");

Cutting the mustard

But we don’t really want to test every single feature. We want to know if the browser is capable enough or not. This is really a boolean test. The BBC came up with a test they called Cutting The Mustard, to see if a browser is really modern. The original test passed on IE9+ and Android Webkit, which you probably don’t want to support today. An updated cut removes those two obsolete browsers.

With the rise of modern JavaScript, we can use module support as a mustard test. If the browser supports modules, it gets your JavaScript, and if it doesn’t, it doesn’t. You’ll need to be a little careful with your CSS though.


<script type="module" src="./mustard.js"></script>


Incompetent Browsers: Browser Grading

Monday, July 28th, 2025

If you want to build web sites that actually work, you have to take into account the wide variety of browsers that might visit, and take steps to make sure that everyone can use your site. The question is, what are you going to do when someone comes to your site in a browser that doesn’t actually work?

A lot of sites just fail, in that they are completely unusable. But a little forethought can keep the basics of your site usable for anyone. If your site is a full-on app and someone comes by in IE3, there’s nothing to do but tell them to come back with a real browser. But you can at least successfully do that! If you’re building a site for a restaurant, people really only want the address and hours, and you should be able to deliver that to any browser.

One of the earliest systematic attempts to handle this issue was Browser Grading: giving plausible browsers a grade for how good they were, and making sure they got appropriate content. For years, the king of browser grading was Yahoo, or actually, the Yahoo UI Library.

Yui evaluated browsers on 4 criteria:

Identified vs. Unknown There are over 10,000 browser brands, versions, and configurations and that number is growing. It is possible to group known browsers together.
Capable vs. Incapable No two browsers have an identical implementation. However, it is possible to group browsers according to their support for most web standards.
Modern vs. Antiquated As newer browser versions are released, the relevancy of earlier versions decreases.
Common vs. Rare There are thousands of browsers in use, but only a few dozen are widely used.

They then gave browsers one of 3 grades: A, C, or X.

A-Grade browsers were identified, capable, modern, and common. They were identified with a whitelist. They got full QA Testing

C-Grade browsers were identified, incapable, antiquated, and rare. They were identified with a blacklist. They got sparse QA Testing, but bugs were still addressed.

X-Grade browsers were all others. They were unidentified and QA did not test them.

A-Grade browsers got the full functionality of the site. X-grade browsers were assumed to be trying, and got the full functionality too.

C-Grade browsers…

C-grade is the base level of support, providing core content and functionality. It is sometimes called core support. Delivered via nothing more than semantic HTML, the content and experience is highly accessible, unenhanced by decoration or advanced functionality, and forward and backward compatible. Layers of style and behavior are omitted.

In February of 2007, YUI’s browser grading chart looked like this:

Win 98 Win 2000 Win XP Mac 10.3.x Mac 10.4
IE 7.0 A-grade
IE 6.0 A-grade A-grade A-grade
Firefox 2.0.* A-grade A-grade A-grade A-grade A-grade
Firefox 1.5.* A-grade A-grade A-grade A-grade A-grade
Opera 9.* A-grade A-grade A-grade A-grade A-grade
Safari 2.0* A-grade

They had just dropped support for IE 5.5, Firefox 1.0, Netscape, and the Mozilla App Suite.

In 2010, they supported at A-grade IE 6, 7, 8 and 9, Safari 5, Firefox 3.6 and 4, Chrome (latest stable), and Android Webkit. C-grade browsers were listed as IE < 6, Safari < 3, Firefox < 3, Opera &lt 9.5, and Netscape &lt 8. They forecast that they would discontinue support for IE 6, dropping it to C-grade.

In 2011, they chickened out, and removed grades from their support page. You can find a list of dead links to their removed blog posts, if you want to grovel around in the internet archive and see what it used to look like. In fact, I wrote this post because all the source documents have been removed from the Internet.

Right as Yahoo was chickening out, jQuery Mobile was trying browser grading too. They had the guts to give out “F”s to Windows Mobile 6.1 and Blackberry 4.5 for a hot minute, if you want to grovel around in the Internet Archive.

They also gave “B”s:

B Medium Quality. Either a lower quality browser with high market share or a high quality browser with low market share.

They eventually settled on full support for A-grade browsers, support without AJAX page transitions for B-grade browsers, and plain but functional HTML for C-grade browsers.