Javascript ES6 cross-browser detection

JavascriptEcmascript 6

Javascript Problem Overview


How can I find out the browser's Javascript engine version and support to ECMAScript 6?

I'm using navigator.appVersion just to know the browser's version, but not the engine's version.

Javascript Solutions


Solution 1 - Javascript

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.

Solution 2 - Javascript

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();

Solution 3 - Javascript

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/

Solution 4 - Javascript

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.

Solution 5 - Javascript

  1. Detect devicePixelRatio which is a special property in WebKit.
  2. 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);
  }

})()

Solution 6 - Javascript

For now there's not a exact way to detect ES6, but if you test its features in the current browser, you can determine if the engine is ES6. My esx library detects the ECMAScript version by doing syntax tests and methods check. For know it can detect ECMAScript 3, 5, 6 and 7 (ES7 not tested, but should work), if no ECMAScript test matched, it gives null as result.

Example using my library:

if (esx.detectVersion() >= 6) {
    /* We're in ES6 or above */
}

Solution 7 - Javascript

As Damian Yerrick has mentioned, the use of eval() or Function() is incompatible with a Content Security Policy that does not specify 'unsafe-eval'.

If the browser supports Worker then you can detect support for any ES6 syntax by implementing that syntax in a worker and checking for error or successs eg to detect support for arrow functions:

worker.js

// If ES6 arrow functions are supported then the worker listener will receive true, otherwise it will receive an error message
(() => {
    postMessage(true);
})();

index.js

if (typeof (Worker) !== "undefined") {
    
    var myWorker = new Worker('worker.js');

    myWorker.onmessage = function (e) {
        // arrow functions must be supported since we received message from the worker arrow function
    }

    myWorker.onerror = function (e) {
        // the worker triggered an error so arrow function not supported (could explicitly check message for syntax error)
    }
}

Solution 8 - Javascript

This function returns true in Chrome 98.0.4758.80 and Firefox 97.0.2 (just tested). It may not work on other browsers and previous versions of Chrome/Firefox (false negative results)

function hasAsyncSupport () {
    return Object.getPrototypeOf(async function() {}).constructor.toString().includes('Async')
}

Solution 9 - Javascript

Put the incompatible syntax code, such as containing arrow functions, in it's own script block and polyfill it with compatible syntax code.

<script>
		// This script block should not compile on incompatible browsers, 
        // leaving the function name undefined.
		// It can then be polyfilled with a function containing compatible syntax code.
		function fame() {
			/* incompatible syntax code such as arrow functions */
		}
</script>

<script>
	if (typeof fame !== "function") {
		// alert("polyfill: fame");
		function fame() {
			/* compatible syntax code */
	    }
    }
</script>

<script>
    // main code
	fame();
</script>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKnock YangView Question on Stackoverflow
Solution 1 - JavascriptMarco BonelliView Answer on Stackoverflow
Solution 2 - JavascriptCaseyView Answer on Stackoverflow
Solution 3 - JavascriptMartin WantkeView Answer on Stackoverflow
Solution 4 - JavascriptMaciej KrawczykView Answer on Stackoverflow
Solution 5 - JavascriptSingView Answer on Stackoverflow
Solution 6 - JavascriptKlaiderView Answer on Stackoverflow
Solution 7 - JavascriptBedrockView Answer on Stackoverflow
Solution 8 - JavascriptmanidosView Answer on Stackoverflow
Solution 9 - JavascriptNOYBView Answer on Stackoverflow