Javascript ES6 cross-browser detection
Feature detection
I suggest you to use feature detection instead of detecting the browser's engine with heuristic methods. To do this you can simply wrap some code inside a try {..} catch (e) {...}
statement, or use some if (...)
statements.
For example:
function check() {
if (typeof SpecialObject == "undefined") return false;
try { specialFunction(); }
catch (e) { return false; }
return true;
}
if (check()) {
// Use SpecialObject and specialFunction
} else {
// You cannot use them :(
}
Why is feature detection better than browser/engine detection?
There are multiple reasons that make, in most of the cases, feature detection the best option:
You don't have to rely on browser's version, engine or specifics, nor detect them using heuristic methods which are hard and pretty crafty to implement.
You will not fall into errors regarding browser/engine specifications detection.
You don't have to worry about browser-specific features: for example WebKit browsers have different specifications than other ones.
You can be sure that, once a feature is detected, you'll be able to use it.
These are the main reasons that IMHO make feature detection the best approach.
Feature detection + fallback
When using feature detection, a pretty smart way to work when you aren't sure which features you can/cannot use consists in several feature detections and consequent fallbacks to more basic methods (or even creation of these methods from scratch) in case the features you want to use are not supported.
A simple example of feature detection with fallback may be applied to the window.requestAnimationFrame
feature, which is not supported by all the browsers, and has several different prefixes depending on the browser you're working on. In this case, you can easily detect and fallback like this:
requestAnimationFrame =
window.requestAnimationFrame // Standard name
|| window.webkitRequestAnimationFrame // Fallback to webkit- (old versions of Chrome or Safari)
|| window.mozRequestAnimationFrame // Fallback to moz- (Mozilla Firefox)
|| false; // Feature not supported :(
// Same goes for cancelAnimationFrame
cancelAnimationFrame = window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || false;
if (!requestAnimationFrame) {
// Not supported? Build it by yourself!
requestAnimationFrame = function(callback) {
return setTimeout(callback, 0);
}
// No requestAnim. means no cancelAnim. Built that too.
cancelAnimationFrame = function(id) {
clearTimeout(id);
}
}
// Now you can use requestAnimationFrame
// No matter which browser you're running
var animationID = requestAnimationFrame(myBeautifulFunction);
ECMAScript 6 (Harmony) features detection
Now, coming to the real problem: if you want to detect the support to ES6, you'll not be able to behave like I said above, because a relevant range of ES6 features is based on new syntaxes and private words, and will throw a SyntaxError
if used in ES5, which means that writing a script which contains both ES5 and ES6 is impossible!
Here is an example to demonstrate this issue; the below snippet won't work, and it will be blocked before execution because contains illegal syntax.
function check() {
"use strict";
try { eval("var foo = (x)=>x+1"); }
catch (e) { return false; }
return true;
}
if (check()) {
var bar = (arg) => { return arg; }
// THIS LINE will always throw a SyntaxError in ES5
// even before checking for ES6
// because it contains illegal syntax.
} else {
var bar = function(arg) { return arg; }
}
Now, since that you cannot both check and execute ES6 conditionally in the same script, you'll have to write two different scripts: one which only uses ES5, and another one which includes ES6 features. With two different scripts you'll be able to import the ES6 one only if it is supported, and without causing SyntaxErrors
to be thrown.
ES6 detection and conditional execution example
Now let's make a more relatable example, and let's say you want to use these features in your ES6 script:
- The new
Symbol
objects - Classes built with the
class
keyword - Arrow (
(...)=>{...}
) functions
NOTE: feature detection of newly introduced syntaxes (like arrow functions) can only be done using the eval()
function or other equivalents (e.g. Function()
), because writing invalid syntax will stop the script before its execution. This is also the reason why you cannot use if
statements to detect classes and arrow functions: these features are regarding keywords and syntax, so an eval(...)
wrapped inside a try {...} catch (e) {...}
block will work fine.
So, coming to the real code:
-
HTML Markup:
<html> <head> <script src="es5script.js"></script> </head> <body> <!-- ... --> </body> </html>
-
Code in your
es5script.js
script:function check() { "use strict"; if (typeof Symbol == "undefined") return false; try { eval("class Foo {}"); eval("var bar = (x) => x+1"); } catch (e) { return false; } return true; } if (check()) { // The engine supports ES6 features you want to use var s = document.createElement('script'); s.src = "es6script.js"; document.head.appendChild(s); } else { // The engine doesn't support those ES6 features // Use the boring ES5 :( }
-
Code in your
es6script.js
:// Just for example... "use strict"; class Car { // yay! constructor(speed) { this.speed = speed; } } var foo = Symbol('foo'); // wohoo! var bar = new Car(320); // blaze it! var baz = (name) => { alert('Hello ' + name + '!'); }; // so cool!
Browser/engine detection
Like I said above, browser and engine detection are not the best practices when programming some JavaScript script. I'm gonna give you some background on this topic, just not to leave my words as a "random personal opinion".
Quoting from the MDN Documentation [link]:
When considering using the user agent string to detect which browser is being used, your first step is to try to avoid it if possible. Start by trying to identify why you want to do it.
[...] Are you trying to check for the existence of a specific feature? Your site needs to use a specific Web feature that some browsers don't yet support, and you want to send those users to an older Web site with fewer features but that you know will work. This is the worst reason to use user agent detection, because odds are eventually all the other browsers will catch up. You should do your best to avoid using user agent sniffing in this scenario, and do feature detection instead.
Also, you're saying you use navigator.appVersion
, but consider using another approach, because that one, together with many other navigator properties, is deprecated, and doesn't always behave like you think.
So, quoting from the MDN Documentation [link] again:
Deprecated: this feature has been removed from the Web standards. Though some browsers may still support it, it is in the process of being dropped. Do not use it in old or new projects. Pages or Web apps using it may break at any time.
Note: Do not rely on this property to return the correct browser version. In Gecko-based browsers (like Firefox) and WebKit-based browsers (like Chrome and Safari) the returned value starts with "5.0" followed by platform information. In Opera 10 and newer the returned version does not match the actual browser version, either.
Browser vendors that support ES6 modules now provide an easy way to do feature detection:
...
<head>
<script nomodule>window.nomodules = true;</script>
<script>console.log(window.nomodules)</script>
</head>
...
The script with the nomodule
attribute will not be excuted by browsers which support <script type="module" ...>
You can also inject the script like this:
const script = document.createElement('script');
script.setAttribute('nomodule', '');
script.innerHTML = 'window.nomodules = true;';
document.head.insertBefore(script, document.head.firstChild);
script.remove();
As Marco Bonelli said, the best way to detect ECMAScript 6 language syntax is to use eval();. If the call does not throw an error, "all other" features are supported, but I recommend Function();.
function isES6()
{
try
{
Function("() => {};"); return true;
}
catch(exception)
{
return false;
}
}
demo: https://jsfiddle.net/uma4Loq7/
No eval ES6 feature detection
You can do it without using eval - just insert the detection code in its own script block and make a global variable assignment at the end. The variable assignment will not run if any error occurs in the script block.
<script>
window.isES6 = false;
</script>
<script>
// Arrow functions support
() => { };
// Class support
class __ES6FeatureDetectionTest { };
// Object initializer property and method shorthands
let a = true;
let b = {
a,
c() { return true; },
d: [1,2,3],
};
// Object destructuring
let { c, d } = b;
// Spread operator
let e = [...d, 4];
window.isES6 = true;
</script>
<script>
document.body.innerHTML += 'isES6: ' + window.isES6;
</script>
https://jsfiddle.net/s5tqow91/2/
Please note that there are many ES6 features, and checking only one does not guarantee you are covered. (The above code doesn't cover everything either, it's just what I think are the features that I use most often).
Why no eval?
The main reason is security and it's not that calling eval for feature detection is insecure by itself. It's that ideally you should disallow eval with Content Security Policy so it cannot be used at all - which greatly decreases attack surface. But if your own code uses eval, you cannot do that.
- Detect devicePixelRatio which is a special property in WebKit.
- Detect javaEnabled function's implement.
(function() {
var v8string = 'function%20javaEnabled%28%29%20%7B%20%5Bnative%20code%5D%20%7D';
var es6string = 'function%20javaEnabled%28%29%20%7B%0A%20%20%20%20%5Bnative%20code%5D%0A%7D';
if (window.devicePixelRatio) //If WebKit browser
{
var s = escape(navigator.javaEnabled.toString());
if (s === v8string) {
alert('V099787 detected');
} else if (s === es6string) {
alert('ES6 detected')
} else {
alert('JSC detected');
}
} else {
display("Not a WebKit browser");
}
function display(msg) {
var p = document.createElement('p');
p.innerHTML = msg;
document.body.appendChild(p);
}
})()