Browser Detection
The fact that different browsers implement HTML5 features differently (or not at all) makes it crucial for your HTML5 application to detect the current browser (and its version number) and adapt accordingly. The information needed to identify the browser and its version number is stored in the userAgent property of the DOM's navigator object. Consider the following userAgent examples:
- Chrome 6.0.472.63: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3
- Firefox 3.6.10: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.10) Gecko/20100914 Firefox/3.6.10
- Firefox 4.0 Beta 6: Mozilla/5.0 (Windows NT 5.1; rv:2.0b6) Gecko/20100101 Firefox/4.0b6
- Internet Explorer 8.0.6001.18702: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)
- Opera 10.63: Opera/9.80 (Windows NT 5.1; U; en) Presto/2.6.30 Version/10.63
- Safari 5.0.2: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.18.1 (KHTML, like Gecko) Version/5.0.2 Safari/533.18.5
Chrome is recognizable via Chrome/, Firefox is recognizable via Firefox/, Internet Explorer is recognizable via MSIE, Opera is recognizable via Opera/, and Safari is recognizable via Safari and Version/ (to differentiate it from Chrome, which also includes Safari). Internet Explorer records its version number after a space character; the other browsers record their version numbers after the / character.
I've created a simple BrowserDetect.js library whose getChromeVersion(), getFirefoxVersion(), getInternetExplorerVersion(), getOperaVersion(), and getSafariVersion() functions return a string containing the version number of the appropriate browser if that browser is current, or null if that browser is not current. Listing 5 presents this library's source code.
Listing 5BrowserDetect.js
function getChromeVersion() { var index = navigator.userAgent.indexOf("Chrome/"); if (index == -1) return null; var i = 7; var version = ""; while ((ch = navigator.userAgent.substr(index+i, 1)) != " ") { version += ch; i++; } return version; } function getFirefoxVersion() { var index = navigator.userAgent.indexOf("Firefox/"); if (index == -1) return null; var i = 8; var length = navigator.userAgent.length; var version = ""; while (index+i != length) { version += navigator.userAgent.substr(index+i, 1); i++; } return version; } function getInternetExplorerVersion() { var index = navigator.userAgent.indexOf("MSIE"); if (index == -1) return null; var i = 5; var version = ""; while ((ch = navigator.userAgent.substr(index+i, 1)) != ";") { version += ch; i++; } return version; } function getOperaVersion() { if (navigator.userAgent.indexOf("Opera") == -1) return null; var index = navigator.userAgent.indexOf("Version/"); if (index == -1) return null; var i = 8; var version = ""; while ((ch = navigator.userAgent.substr(index+i, 1)) != "") { version += ch; i++; } return version; } function getSafariVersion() { if (navigator.userAgent.indexOf("Safari") == -1) return null; var index = navigator.userAgent.indexOf("Version/"); if (index == -1) return null; var i = 8; var version = ""; while ((ch = navigator.userAgent.substr(index+i, 1)) != " ") { version += ch; i++; } return version; } function cmp(a, b) { var aFields = a.split("."); var bFields = b.split("."); var numFields = aFields.length; if (bFields.length < numFields) numFields = bFields.length; for (var i = 0; i < numFields; i++) { try { var x = parseInt(aFields[i]); var y = parseInt(bFields[i]); if (x != y) return x-y; } catch(e) { alert(e); return NaN; } } if (aFields.length == bFields.length) return 0; else if (aFields.length > bFields.length) return 1; else return -1; } // cmp() compares the strings passed to a and b. It assumes that each string // is formatted as a sequence of period-separated integer fields (e.g., // 6.0.472.63), and returns one of the following values: // // * a positive value returns if a > b // * a zero value returns if a = b // * a negative value returns if a < b // * NaN if one of a's or b's integer fields is found to be invalid (doesn't // begin with a digit, for example) // // Examples: // // alert(cmp("4.06", "4.06")); // Output: 0 // alert(cmp("10.0.612.3", "6.0.472.63")); // Output: 4 // alert(cmp("6.0.472.63", "10.0.612.3")); // Output: -4 // alert(cmp("1.2.3", "1.2")); // Output: 1 // alert(cmp("1.2", "1.2.3")); // Output: -1 // alert(cmp("3", "")); // Output: NaN
Function cmp(a, b) compares the version number strings passed to parameters a and b. Each string must be a sequence of period-separated integer values (such as 6.0.472.63). For example, cmp("10.0.612.3", "6.0.472.63") returns a positive value indicating that "10.0.612.3" is greater than "6.0.472.63". In contrast, "10.0.612.3" >= "6.0.472.63" evaluates to false because 1 is less than 6.
I've also created a small TestBrowserDetect.html file for testing these functions under the Chrome, Firefox, Internet Explorer, Opera, and Safari browsers. Listing 6 presents the contents of this file.
Listing 6TestBrowserDetect.html
<html> <head> <title> Test BrowserDetect.js </title> <script type="text/javascript" src="BrowserDetect.js"> </script> </head> <body> <script type="text/javascript"> document.writeln("Chrome: "+getChromeVersion()); document.writeln("<br>"); document.writeln("Firefox: "+getFirefoxVersion()); document.writeln("<br>"); document.writeln("Internet Explorer: "+getInternetExplorerVersion()); document.writeln("<br>"); document.writeln("Opera: "+getOperaVersion()); document.writeln("<br>"); document.writeln("Safari: "+getSafariVersion()); </script> </body> </html>
Figure 4 reveals TestBrowserDetect.html file in the context of the Internet Explorer 8 browser.
Figure 4 TestBrowserDetect.html identifies Internet Explorer 8 as the current browser.
Conclusion
HTML5 has much to offer, and you'll probably want to create HTML5 applications that function in any browser. Because support for HTML5 features varies from browser to browser, you should test each desired feature with these browsers, and design your applications to detect the current browser and adapt to its HTML5 limitations. I focus on designing portable HTML5 applications in Parts 2 and 3 of this series.