- Last update: 2017/08/17
- Version: 2.0.0
<!-- Detect the browser support-level richplastow.com/w4#b4 2017/08/17 -->
<script>!function(u,H,L){u=u.replace('ozilla/','ozilla-');var p,c,t,i,j,k,n,v,m,
V=' Version/',M='match',C=this.CustomEvent;f({w:'indows|; Win|(Win',a:'Android',
i:'iOS|iPh|iPo|iPa',m:'Mac OS X',l:'X11|Linux',_:' '});p=c;f({b:'ungBr|SAMSU',e:
' Edge/',u:' UCBr',l:'Links (|Lynx/',m:'Opera Mi',o:'OPiOS/|OPR/|Opera/|Opera ',
c:'CriOS/|Chrome/',f:'FxiOS/|Firefox/',i:'MSIE |Tride',a:'Android ',s:'Safari/|'
+'ebKit/',_:'/v|'+V});if('Tride'==n)i=u.indexOf('rv:')-4;k=0<=(j=u.indexOf(V))&&
!c[M](/[mca]/)?j+9:n?i+n.length:i;if(n)v=u.slice(k)[M](/^\s|\d+/);v=null!=v?+v:(
j=u[M](/[:\/]\d+\.\d+/g))?+j.pop()[M](/\d+/):(j=u[M](/[- v]\d+\.\d+/g))?+j.pop()
[M](/\d+/):0;if('s'==c&&84<v)for (t=v,k=v=0;t>k;k+=([411,8,106,7,3,1,1,63,1
][v++]));B:for(i=0;j=L[i];i++)for(t=0;m=j.split(' ')[t++];)if(c==m.charAt(0)&&v<
m.slice(1))break B;function f(X){for(c in X)for(t=0;n=X[c].split('|')[t++];)if(0
<=(i=u.indexOf(n)))return}H.className+=[null,'s'+i,c,'v'+v,'d'+('m'==c?'t':u[M](
/bot|crawl|spide|searc|okext/i)?'b':u[M](/Ninte|PS[P3]|YSTA|ySta|Xbo/)?'g':u[M](
/iP[aho]|[Tt]able|Android|obile|ckBer|Noki|Symb[iO]| UP\.Br|NetFr|J2ME|SonyEr/)?
't':'d'),'p'+p].join(' b4');if(C)dispatchEvent(new C('b4-'+(i?'ok':'fail')))}(
navigator.userAgent,document.lastChild,['b2 e12 o10 c15 f3 i6 a2 s4'])</script>
b4 has no dependencies, so you can place the minified code anywhere. When used alongside other w4 snippets, it’s placed near the top - between a4 and c4.
- It’s usual to keep the first argument set to
navigator.userAgent
. But for testing or spoofing you can set your own user agent string here. - It’s usual to keep second argument set to
document.lastChild
(which is a succinct way of referencing the<html>
element). But for testing you could pass in any object with a string property namedclassName
. - You should change
['b2 e12 o10 c15 f3 i6 a2 s4']
to a list of browser name-codes and versions which you want to support.
b4 adds five classes to the <html>
element. It also triggers a 'b4-ok' or
'b4-fail' event on the window — if you have a4 installed, it will
convert these to "b4-ok" or "b4-fail" classes.
For example, Firefox 54 on desktop Mac OS X might look something like this:
<html class="b4s1 b4f b4v54 b4dd b4pm b4-ok">
If you define two support levels, and a4 is running, then the possible classes are:
b4-ok b4s2
The user agent is fully supported, and can run all app featuresb4-ok b4s1
Legacy mode (medium-age browser, not all features supported)b4-fail b4s0
The user agent is too old, and cannot even run in legacy mode
- b4a The Android browser
- b4b Samsung Browser
- b4c Chrome
- b4e Microsoft Edge
- b4f Firefox
- b4i Internet Explorer
- b4m Opera Mini
- b4o Opera
- b4s Safari
- b4u UC Browser
- b4_ Underscore signifies name not recognised
There may be cases where this is useful - you could target a particular version of Firefox in CSS using:
html.b4f.b4v55 .something {
color: red;
}
- b4dc Crawler, or some other kind of bot
- b4dd Desktop or laptop - likely to have a physical keyboard
- b4dg Games console, eg PS3
- b4dt Tablet or Phone - likely to be touchscreen
If not recognised, ‘Device’ defaults to "b4dd".
- b4pa Android (tablet, phone or TV)
- b4pi iOS (tablet, phone or TV)
- b4pl Linux (desktop)
- b4pm Mac OS X (desktop)
- b4pw Windows (desktop)
- b4p_ Underscore signifies platform not recognised
- The fully indented and commented code below
- The Usage Example below
- The unit test
- richplastow.com’s source code
Here’s a fully indented and commented version of the Minified code:
<!-- Detect the browser support-level richplastow.com/w4#b4 2017/08/17 -->
<script>
!function ( // use a closure to avoid polluting global scope (pre ES6)
u // the user-agent string, from `navigator.userAgent`
, H // the <html> element
, L // levels of support - an array of space-delimited strings, where each
// string represents an ascending level of support
) {
//// Prevent leading 'Mozilla/5.0' from being parsed as the version,
//// unless all else fails.
u = u.replace('ozilla/','ozilla-')
var p // platform code - one of a, i, l, m, w, _
, c // name code - one of b, e, u, l, m, o, c, f, i, a, s, _
, t // part-index, eg 'OPiOS/|OPR/|Opera/' has three parts
, i // index of user agent name - then reused for ‘level of support’
, j // index of 'Version/' - then reused to hold a space-delimited string
, k // index of the start of the version
, n // matching name string, eg 'msungBrowser/'
, v // version, will be 0 if the version cannot be determined
, m // an individual minimum user agent version
, V = ' Version/' // helps minimise bytes
, M = 'match' // helps minimise bytes
, C = this.CustomEvent
//// Get `p`, the user agent’s platform.
f({
w: 'indows|; Win|(Win' //
, a: 'Android' //
, i: 'iOS|iPh|iPo|iPa' //
, m: 'Mac OS X' //
, l: 'X11|Linux' // Linux
, _: ' ' // platform not recognised
})
p = c
//// Get `n`, the user agent’s namecode.
f({
b: 'ungBr|SAMSU' // Samsung browser
, e: ' Edge/' // Microsoft Edge
, u: ' UCBr' // UC Browser
, l: 'Links (|Lynx/' // Various ‘textmode’ user-agents
, m: 'Opera Mi' // Opera Mini
, o: 'OPiOS/|OPR/|Opera/|Opera ' // Opera
, c: 'CriOS/|Chrome/' // Chrome
, f: 'FxiOS/|Firefox/' // Firefox
, i: 'MSIE |Trident' // Internet Explorer (use 'rv:' to get v)
, a: 'Android ' // Android Webkit Browser
, s: 'Safari/|ebKit/' // Safari
, _: '/v|' + V // name not recognised
})
//// if 'Trident', use 'rv:' to get the version.
if ('Trident' == n)
i = u.indexOf('rv:') - 4 // `4` is 'Trident'.length - 'rv:'.length
//// Get the user agent’s version.
k = // find it after ' Version/' if available, unless Op’Mini/Chrome/Android
0 <= (j=u.indexOf(V)) && ! c[M](/[mca]/) ? j + 9
: n ? i + n.length : i
if (n) v = u.slice(k)[M](/^\s|\d+/) // get digits after matched string
v = null != v // found '123' or ' ' after matched string...
? +v // ...so convert it to a number (note, 'Chrome/ ' is version `0`)
: (j = u[M](/[:\/]\d+\.\d+/g)) // else get each ':12.3' or '/4.56'
? +j.pop()[M](/\d+/) // use first digit from last occurance, else
: (j = u[M](/[- v]\d+\.\d+/g)) // get each '-1.2', ' 3.4', 'v5.6'
? +j.pop()[M](/\d+/) // use first digit from last occurance
: 0
//// Convert a WebKit version to a Safari version (updated July 19 2017).
// 1 / 85 to 312.6
// 2 / 412 to 419.3
// 3 / 420 to 525.29.1
// 4 / 526.11.2 to 533.19.4
// 5 / 533.16 to 534.59.10
// 6 / 536.25 to 537.85.17
// 7 / 537.71 to 537.85.17
// 8 / 538.35.8 to 600.7.12
// 9 / 601.1.56 to 601.7.8
// 10 / 602.1.50 to 603.3.8
if ('s' == c && 84 < v)
for (t=v,k=v=0; t>k; k+=([411,8,106,7,3,1,1,63,1][v++])); // same as...
// for (t=v,v=0; t>[0,411,419,525,532,535,536,537,600,601][++v];);
//// Determine the level at which the current user agent supported.
B: // break-label, to break out of the two-level loop
for (i=0;j=L[i];i++) // step through each level
for (t=0; m=j.split(' ')[t++];) // 'o10 c15' becomes 'o10', 'c15'
if (c == m.charAt(0) // found a matching namecode, whose...
&& v < m.slice(1) ) // ...version is too low (coerce str to num)
break B // so the ua is not supported at this level
//// Finds OScode and namecode.
function f (X) {
for (c in X) // `x` is each code
for (t=0; n=X[c].split('|')[t++];) // `n` is 'iOS', then 'iPh' etc
if ( 0 <= (i=u.indexOf(n)) ) // if found, `i` is start-index
return
}
//// Add the various `b4` classes to the <html> element.
//// '/bot|crawl|spider/i' is a reduction of stackoverflow.com/a/20084661
H.className += // `classList` is not supported in older user agents
[ null // makes `join()` add a ' b4-' in here
, 's' + i // "b4s0" if the user agent is completely unsupported
, c // the name code, one of b, e, u, l, m, o, c, f, i, a, s, _
, 'v' + v // major-version, recorded as an integer (0 means unknown)
, 'd' + ( // the device code, one of b, d, g, t
'm' == c // Opera Mini
? 't' // phone or tablet
: u[M](/bot|crawl|spide|searc|okext/i)
? 'b' // hardwarecode 'b' if bot
: u[M](/Ninte|PS[P3]|YSTA|ySta|Xbo/)
? 'g' // games console
: u[M](/iP[aho]|[Tt]ablet|Android|Mobile|ckBer|Nokia|Symb[iO]| UP\.Br|NetFron|J2ME|SonyEr/)
// Symbian, SymbianOS, SymbOS
? 't' // phone or tablet
: 'd' // desktop
)
, 'p' + p // platform, one of a, i, l, m, w, _
].join(' b4')
//// Trigger success of failure - a4 converts this to an <html> class.
if (C)
dispatchEvent( // you can `addEventListener()` for two events...
new C(
'b4-' + (i ? 'ok' : 'fail') // ...'b4-fail' and 'b4-ok'
) // `i` is `true` if the user agent is supported, `false` if not
)
}(navigator.userAgent, document.lastChild, ['b2 e12 o10 c15 f3 i6 a2 s4'])
</script>
@TODO
Created by Rich Plastow, during development of richplastow.com.
- Homepage: richplastow.com
- LinkedIn: richardplastow
- GitHub: richplastow
- Twitter: @RichPlastow
- Location: Brighton, UK
- Android: Android Browser 2+, Chrome 34+, Firefox 51+, Samsung 3+
- iOS 6: UC Browser 10
- iOS 3: Safari 4
- Windows 10: Edge 14+, IE 11
- Windows 7: IE 10, Yandex 14 (identified as Chrome 38)
- Windows XP: Firefox 3+, Chrome 15+, Safari 4+, Opera 10+, IE 6+
- OS X Snow Leopard: Safari 4+
- 1.0.0 Enough for richplastow.com, but needs tests before wider adoption
- 1.0.1 Outputs to
<html class="...">
, not window.b4 - 2.0.0 New class-names and lots of unit tests