Fixed logic error - all tests were passing... :(
[mediawiki.git] / resources / jquery / jquery.client.js
blob38aa61552b42445724d6394654c6e60dd514f594
1 /**
2  * User-agent detection
3  */
4 $.client = new ( function() {
6         /* Private Members */
8         var profile;
10         /* Public Functions */
12         /**
13          * Returns an object containing information about the browser
14          *
15          * The resulting client object will be in the following format:
16          *      {
17          *              'name': 'firefox',
18          *              'layout': 'gecko',
19          *              'layoutVersion': '20101026',
20          *              'platform': 'linux'
21          *              'version': '3.5.1',
22          *              'versionBase': '3',
23          *              'versionNumber': 3.5,
24          *      }
25          */
26         this.profile = function() {
27                 // Use the cached version if possible
28                 if ( typeof profile === 'undefined' ) {
30                         /* Configuration */
32                         // Name of browsers or layout engines we don't recognize
33                         var uk = 'unknown';
34                         // Generic version digit
35                         var x = 'x';
36                         // Strings found in user agent strings that need to be conformed
37                         var wildUserAgents = [ 'Opera', 'Navigator', 'Minefield', 'KHTML', 'Chrome', 'PLAYSTATION 3'];
38                         // Translations for conforming user agent strings
39                         var userAgentTranslations = [
40                             // Tons of browsers lie about being something they are not
41                                 [/(Firefox|MSIE|KHTML,\slike\sGecko|Konqueror)/, ''],
42                                 // Chrome lives in the shadow of Safari still
43                                 ['Chrome Safari', 'Chrome'],
44                                 // KHTML is the layout engine not the browser - LIES!
45                                 ['KHTML', 'Konqueror'],
46                                 // Firefox nightly builds
47                                 ['Minefield', 'Firefox'],
48                                 // This helps keep differnt versions consistent
49                                 ['Navigator', 'Netscape'],
50                                 // This prevents version extraction issues, otherwise translation would happen later
51                                 ['PLAYSTATION 3', 'PS3'],
52                         ];
53                         // Strings which precede a version number in a user agent string - combined and used as match 1 in
54                         // version detectection
55                         var versionPrefixes = [
56                                 'camino', 'chrome', 'firefox', 'netscape', 'netscape6', 'opera', 'version', 'konqueror', 'lynx',
57                                 'msie', 'safari', 'ps3'
58                         ];
59                         // Used as matches 2, 3 and 4 in version extraction - 3 is used as actual version number
60                         var versionSuffix = '(\\/|\\;?\\s|)([a-z0-9\\.\\+]*?)(\\;|dev|rel|\\)|\\s|$)';
61                         // Names of known browsers
62                         var names = [
63                                 'camino', 'chrome', 'firefox', 'netscape', 'konqueror', 'lynx', 'msie', 'opera', 'safari', 'ipod',
64                                 'iphone', 'blackberry', 'ps3'
65                         ];
66                         // Tanslations for conforming browser names
67                         var nameTranslations = [];
68                         // Names of known layout engines
69                         var layouts = ['gecko', 'konqueror', 'msie', 'opera', 'webkit'];
70                         // Translations for conforming layout names
71                         var layoutTranslations = [['konqueror', 'khtml'], ['msie', 'trident'], ['opera', 'presto']];
72                         // Names of supported layout engines for version number
73                         var layoutVersions = ['applewebkit', 'gecko'];
74                         // Names of known operating systems
75                         var platforms = ['win', 'mac', 'linux', 'sunos', 'solaris', 'iphone'];
76                         // Translations for conforming operating system names
77                         var platformTranslations = [['sunos', 'solaris']];
79                         /* Methods */
81                         // Performs multiple replacements on a string
82                         function translate( source, translations ) {
83                                 for ( var i = 0; i < translations.length; i++ ) {
84                                         source = source.replace( translations[i][0], translations[i][1] );
85                                 }
86                                 return source;
87                         };
89                         /* Pre-processing  */
91                         var userAgent = navigator.userAgent, match, name = uk, layout = uk, layoutversion = uk, platform = uk, version = x;
92                         if ( match = new RegExp( '(' + wildUserAgents.join( '|' ) + ')' ).exec( userAgent ) ) {
93                                 // Takes a userAgent string and translates given text into something we can more easily work with
94                                 userAgent = translate( userAgent, userAgentTranslations );
95                         }
96                         // Everything will be in lowercase from now on
97                         userAgent = userAgent.toLowerCase();
99                         /* Extraction */
101                         if ( match = new RegExp( '(' + names.join( '|' ) + ')' ).exec( userAgent ) ) {
102                                 name = translate( match[1], nameTranslations );
103                         }
104                         if ( match = new RegExp( '(' + layouts.join( '|' ) + ')' ).exec( userAgent ) ) {
105                                 layout = translate( match[1], layoutTranslations );
106                         }
107                         if ( match = new RegExp( '(' + layoutVersions.join( '|' ) + ')\\\/(\\d+)').exec( navigator.userAgent.toLowerCase() ) ) {
108                                 layoutversion = parseInt(match[2]);
109                         }
110                         if ( match = new RegExp( '(' + platforms.join( '|' ) + ')' ).exec( navigator.platform.toLowerCase() ) ) {
111                                 platform = translate( match[1], platformTranslations );
112                         }
113                         if ( match = new RegExp( '(' + versionPrefixes.join( '|' ) + ')' + versionSuffix ).exec( userAgent ) ) {
114                                 version = match[3];
115                         }
117                         /* Edge Cases -- did I mention about how user agent string lie? */
119                         // Decode Safari's crazy 400+ version numbers
120                         if ( name.match( /safari/ ) && version > 400 ) {
121                                 version = '2.0';
122                         }
123                         // Expose Opera 10's lies about being Opera 9.8
124                         if ( name === 'opera' && version >= 9.8) {
125                                 version = userAgent.match( /version\/([0-9\.]*)/i )[1] || 10;
126                         }
128                         /* Caching */
130                         profile = {
131                                 'name': name,
132                                 'layout': layout,
133                                 'layoutVersion': layoutversion,
134                                 'platform': platform,
135                                 'version': version,
136                                 'versionBase': ( version !== x ? new String( version ).substr( 0, 1 ) : x ),
137                                 'versionNumber': ( parseFloat( version, 10 ) || 0.0 )
138                         };
139                 }
140                 return profile;
141         };
143         /**
144          * Checks the current browser against a support map object to determine if the browser has been black-listed or
145          * not. If the browser was not configured specifically it is assumed to work. It is assumed that the body
146          * element is classified as either "ltr" or "rtl". If neither is set, "ltr" is assumed.
147          *
148          * A browser map is in the following format:
149          *      {
150          *              'ltr': {
151          *                      // Multiple rules with configurable operators
152          *                      'msie': [['>=', 7], ['!=', 9]],
153          *                      // Blocked entirely
154          *                      'iphone': false
155          *              },
156          *              'rtl': {
157          *                      // Test against a string
158          *                      'msie': [['!==', '8.1.2.3']],
159          *                      // RTL rules do not fall through to LTR rules, you must explicity set each of them
160          *                      'iphone': false
161          *              }
162          *      }
163          *
164          * @param map Object of browser support map
165          *
166          * @return Boolean true if browser known or assumed to be supported, false if blacklisted
167          */
168         this.test = function( map ) {
169                 var profile = $.client.profile();
170                 var dir = $( 'body' ).is( '.rtl' ) ? 'rtl' : 'ltr';
171                 // Check over each browser condition to determine if we are running in a compatible client
172                 if ( typeof map[dir] !== 'object' || typeof map[dir][profile.name] === 'undefined' ) {
173                         // Unknown, so we assume it's working
174                         return true;
175                 }
176                 var name = map[dir][profile.name];
177                 for ( var condition in name ) {
178                         var op = name[condition][0];
179                         var val = name[condition][1];
180                         if ( val === false ) {
181                                 return false;
182                         } else if ( typeof val == 'string' ) {
183                                 if ( !( eval( 'profile.version' + op + '"' + val + '"' ) ) ) {
184                                         return false;
185                                 }
186                         } else if ( typeof val == 'number' ) {
187                                 if ( !( eval( 'profile.versionNumber' + op + val ) ) ) {
188                                         return false;
189                                 }
190                         }
191                 }
192                 return true;
193         }
194 } )();
196 $( document ).ready( function() {
197         var profile = $.client.profile();
198         $( 'html' )
199                 .addClass( 'client-' + profile.name )
200                 .addClass( 'client-' + profile.name + '-' + profile.versionBase )
201                 .addClass( 'client-' + profile.layout )
202                 .addClass( 'client-' + profile.platform );
203 } );