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