JavaScript Es6 Cross-Browser Detection

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.

How to test if browser supports js ES6 Class?

Technically you could

try { eval('"use strict"; class foo {}'); } catch (e) { console.log(e); }

practically I don't see it's reasonable to prefer it to transpiling at least these days.

How to check if 'let' is supported by the browser?

The only way to feature-detect a new syntax or keyword, is to eval it (or, pass it to the not-much-better Function constructor):

function detectLet(){
try{
return !!new Function('let x=true;return x')()
}catch(e){
return false
}
}

console.log(detectLet())

But also note that you can't conditionally use a new syntax, or try to catch a syntax error, since syntax errors happen before your code starts to run!

So, to conditionally use such a feature, you also need eval, which is even worse...

if(detectLet()){
let foo = 'bar';
}
//SyntaxError if `let` isn't supported
if(detectLet()){
eval("let foo = 'bar';") //Wait... really?!
}
//No errors

Conclusion:

If you need to support (those really old) platforms, that don't support let, then don't use let.

Alternatively, you can transpile your code with tools like Babel

But as of 2022, all major browsers support let (even IE!!!), so you can use it safely, and drop support for really legacy browsers.

How to determine if a Promise is supported by the browser

Update Dec 11 - 2016: All evergreen versions of browsers now support promises. They are safe to use.


Update Nov 14 - 2016: Chrome, Firefox, Safari and IE all now have experimental support for promises in their dev channels. The specification has settled. I would still not rely on the implementation just yet and would use a library but this might change in a few months.


No browsers support promises natively in a reliable way. The specification might change - at least for a few more months. My suggestion is use a fast promise library like Bluebird.

If you want to check if native promises are enabled - you can do :

if(typeof Promise !== "undefined" && Promise.toString().indexOf("[native code]") !== -1){
//here
}

As others suggested, just checking if there is a Promise object can be done by if(Promise) but I strongly suggest against it since different libraries have different APIs for creation of promises etc.

How to detect unpopular browsers (baidu) using JavaScript?

You can detect popular browsers in JavaScript with the following code.

// Mobile Browsers
export const isMobile = () => {
const ua = (navigator || {}).userAgent;
if (ua) {
return (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile|mobile|CriOS/i.test(ua))
}
return false;
}

// Opera 8.0+
export const isOpera = () => {
let opr = window.opr || {};
return (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
}

// Firefox 1.0+
export const isFirefox = () => typeof InstallTrigger !== 'undefined';

// Safari 3.0+ "[object HTMLElementConstructor]"
export const isSafari = () => {
let safari = window.safari || {};
return /constructor/i.test(window.HTMLElement) || (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window['safari'] || (typeof safari !== 'undefined' && safari.pushNotification));
}

// Internet Explorer 6-11
export const isIE = () => /*@cc_on!@*/false || !!document.documentMode;

// Edge 20+
export const isEdge = () => !isIE() && !!window.StyleMedia;

// Chrome 1+
export const isChrome = () => !!window.chrome && !!window.chrome.loadTimes;

So anything that doesn't match any of these popular browsers, will be an unpopular browser.



Related Topics



Leave a reply



Submit