Failing feature detection
Published: 2022-02-14 09:45:34

Add some "magic words" to your browsers' [user agent](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent) string, like "`Vivaldi/5.0.2497.51`" or "`Opera/Edge/OPR/EDG/FCK/WTF`", and get warnings about unsupported browsers, reduced functionality, and Google drive completely refusing to start at all. Why?

I mean, why try? But also: what's wrong with those web apps?

**A browser that must not be named?**

I recently tried to reproduce the [reason why Vivaldi stopped adding their brand](https://vivaldi.com/blog/user-agent-changes/) to user agent strings. The current Vivaldi version basically pretends to be Google Chrome. So that no website complains, but it also won't show up as Vivaldi in browser statistics either and Google thinks everyone is using their own browser version.

## Assumptions and false conclusions

Even the "Pro Webmasters" of [StackExchange](https://stackexchange.com) are affected by trying to do feature detection based on what must not be part of a browser string.

![Screenshot of StackExchange notification: sorry, we no longer support your browser
Please upgrade to Microsoft Edge, Google Chrome, or Firefox. Learn more about our browser support.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sihj6jsr3yuks2klf4a7.png)

StackExchange notification:
> Sorry, we no longer support your browser
Please upgrade to Microsoft Edge, Google Chrome, or Firefox. Learn more about our browser support.

What nonsense!

It's the very same browser, just another user agent string. Ever heard of feature detection? Ever heard of usability, accessibility, progressive enhancement? How can developers be so lost in 2022?

Let's not fall into that trap of making false assumptions as web developers.

### Assumptions based on parsing user agent strings

Just because someone is using (or claims to be using) a certain version of Google Chrome doesn't imply their browser behaves like *your* version of Google Chrome or like specified in a documentation. Customers might use ad blocking software or have restrictions imposed by their operating system or by their companies' administrators for the sake of security.

On the other hand, the presence of a certain combination of letters and numbers doesn't imply a missing feature either.

And remember that [string parsing is inaccurate and error prone](https://softwareengineering.stackexchange.com/questions/113237/when-you-should-not-use-regular-expressions) in general, and that the current user agents are already the result of trying to undermine early browser detection that tried to parse version numbers using naive regular expressions that failed once the browsers adopted version numbers greater than 9.

Opera and Edge are the most popular examples of browsers that switched rendering engines but kept their brand names. They still had to use switch user agent strings, otherwise their users would have missed out on many full featured websites despite running the latest Chromium engine internally.

## Feature detection

Do we need to detect features at all?

HTML and CSS are robust languages that make it easy to design for progressive enhancement as unsupported features are simply ignored.

```css
.sticky {
  position: relative;
  position: -webkit-sticky;
  position: sticky;
```

In CSS, you can write a fallback chain simply by trying out features, starting with the most basic one as potential fallbacks. If that's not enough, we have [feature queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Conditional_Rules/Using_Feature_Queries).

```css
@supports (aspect-ratio: 1) {
  height: auto;
  aspect-ratio: 369 / 493;
}
```

### Misusing feature queries

The following example is not much better than parsing user agent strings. As I did not trust a `-webkit`-prefixed feature used in a [typical iOS hack](https://www.bram.us/2020/05/06/100vh-in-safari-on-ios/) to fix only the iPhone problem (both "AppleWebkit" as well as Chrome's "Webkit" might implement "webkit" features, sometimes even Firefox does) and I'm too lazy to test that it does not harm in other browsers, I tried to restrict the feature to mobile Safari using a feature that is supposedly only supported by that single browser.

```css
/* work around mobile safari 100vh height */
@supports (-webkit-touch-callout: none) {
  body.is-burgermenu-open {
    height: -webkit-fill-available;
```

What a bullshit! This works, but it works for the wrong reasons.

## Network speed vs. form factor

With mobile devices like iPhone, and more multimedia content like web videos, we had to make even more assumptions about our users and their preferences.

Adaptive (as opposed to responsive) web design usually discriminates user agents based on the viewport width, using [media queries](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries), [image source sets](https://dev.to/ingosteinke/responsive-background-images-with-image-set-the-srcset-for-background-image-259a), and [matchMedia()](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia).

Developers often went further and assumed that mobile users might be the ones on a slower network or using more expensive plans paid by data volume. So it seemed like a great usability achievement not to send large amounts of data to "mobile users".

But there are laptops. Working in a café that does not offer free WiFi, I might open a hotspot to use my mobile phone to share a network connection. Same data plan applies, but now the end-user's device has a larger screen and won't be recognized as "mobile" anymore.

Another user might have WiFi or landline network on a classic personal computer and still have a very slow network speed in their rural area.

Okay, let's use the [Network Information API
](https://developer.mozilla.org/en-US/docs/Web/API/Network_Information_API) then. Measure the network connection speed at a given moment, classified in a set of categories represented by string values that cannot be compared numerically, I might make a quick assumption that a user can't download a video with a network as slow as "2G".

```javascript
  const connection = navigator.connection
    || navigator.mozConnection
    || navigator.webkitConnection;
  if (!connection || (
    !connection.saveData
    && connection.type !== 'none'
    && connection.effectiveType !== 'slow-2g'
    && connection.effectiveType !== '2g'
    && connection.effectiveType !== 'slow-3g'
  )) {
    videoInitializeAutoplay();
```

Prone to fail once again. What if there was a temporary glitch exactly at the moment my application tried to assess the network speed? What if the network speed was good when measuring but drops soon afterwards? And why auto-play anyway?

And why didn't I take into account if the user [prefers reduced motion](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion)? It's also [possible in JavaScript](https://since1979.dev/respecting-prefers-reduced-motion-with-javascript-and-react/).

```javascript
const mediaQuery = window.matchMedia(
  "(prefers-reduced-motion: reduce)"
);

if (!mediaQuery || mediaQuery.matches) {
  doSomethingWithoutAnimation();
} else {
  doSomethingWithAnimation();
}
```

### Optimizing web video

[Doug Sillars](https://dougsillars.com/) has done extensive research on web video optimization.

{% youtube https://www.youtube.com/watch?v=rUw4sAV-dJw  %}

Doug found out that many videos fail to start. So let's talk about dealing with failure on a website.

## Graceful degradation

If a feature fails, let's not make it break the whole web application. If a video fails to load, there is no video. Fine. There should be placeholder image anyway, maybe a link to download the video or share it using an external link to Vimeo, and some accessible content for those who can't watch videos for any reason.

In JavaScript, don't assume values and objects, but make sure that they're actually defined!

```javascript
/**
 * @param {HTMLElement=} container (optional) ancestor
 */
function modalOpen(container) {
  const containerElement = container || document;
  if (containerElement) {
    containerElement.classList.add('open');
```

Otherwise you can just hope that browsers will tolerate the error and still try to execute the rest of your script. Likely they won't, though.

![Internet Explorer screenshot: null or not an object](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3siycq3a5o0w1d6ds0gf.png)
 
But that's fine, isn't it?. If your website doesn't work in any browser, simply blame the user and tell them to switch their browser. Even if it works, still tell them. Who doesn't like to see some patronizing annoyance on their screen when searching for important information?

Why bother trying to make accessible websites if you are bound to fail anyway?&lt;/irony>

## You've seen the fallout

> You’ve seen the fallout when digital products aren’t designed for real people. You understand the importance of compassion. And you’ve learned how to talk with users to uncover their deepest feelings and needs. But even with the best intentions, it’s still easy for thoughtful design teams to get lost along the way.

Excerpt from Eric A. Meyer and Sara Wachter-Boettcher's classic book, [Design for Real Life](https://alistapart.com/article/design-for-real-life-excerpt/).

Disclaimer: all the bad code examples in this article are my own. Sure you can do better than me. Let's learn from our mistakes and write less bad code in the future!