summaryrefslogtreecommitdiff
path: root/resources/jquery/jquery.client.js
blob: b0bd6850b6b0975e4475fab5abc19062bd429d7d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
/**
 * User-agent detection
 */
( function ( $ ) {

	/* Private Members */

	/**
	 * @var profileCache {Object} Keyed by userAgent string,
	 * value is the parsed $.client.profile object for that user agent.
	 */
	var profileCache = {};

	/* Public Methods */

	$.client = {

		/**
		 * Get an object containing information about the client.
		 *
		 * @param nav {Object} An object with atleast a 'userAgent' and 'platform' key.
		 * Defaults to the global Navigator object.
		 * @return {Object} The resulting client object will be in the following format:
		 *  {
		 *   'name': 'firefox',
		 *   'layout': 'gecko',
		 *   'layoutVersion': 20101026,
		 *   'platform': 'linux'
		 *   'version': '3.5.1',
		 *   'versionBase': '3',
		 *   'versionNumber': 3.5,
		 *  }
		 */
		profile: function ( nav ) {
			/*jshint boss: true */

			if ( nav === undefined ) {
				nav = window.navigator;
			}
			// Use the cached version if possible
			if ( profileCache[nav.userAgent] === undefined ) {

				var
					versionNumber,

					/* Configuration */

					// Name of browsers or layout engines we don't recognize
					uk = 'unknown',
					// Generic version digit
					x = 'x',
					// Strings found in user agent strings that need to be conformed
					wildUserAgents = ['Opera', 'Navigator', 'Minefield', 'KHTML', 'Chrome', 'PLAYSTATION 3'],
					// Translations for conforming user agent strings
					userAgentTranslations = [
						// Tons of browsers lie about being something they are not
						[/(Firefox|MSIE|KHTML,\slike\sGecko|Konqueror)/, ''],
						// Chrome lives in the shadow of Safari still
						['Chrome Safari', 'Chrome'],
						// KHTML is the layout engine not the browser - LIES!
						['KHTML', 'Konqueror'],
						// Firefox nightly builds
						['Minefield', 'Firefox'],
						// This helps keep differnt versions consistent
						['Navigator', 'Netscape'],
						// This prevents version extraction issues, otherwise translation would happen later
						['PLAYSTATION 3', 'PS3']
					],
					// Strings which precede a version number in a user agent string - combined and used as match 1 in
					// version detectection
					versionPrefixes = [
						'camino', 'chrome', 'firefox', 'iceweasel', 'netscape', 'netscape6', 'opera', 'version', 'konqueror',
						'lynx', 'msie', 'safari', 'ps3'
					],
					// Used as matches 2, 3 and 4 in version extraction - 3 is used as actual version number
					versionSuffix = '(\\/|\\;?\\s|)([a-z0-9\\.\\+]*?)(\\;|dev|rel|\\)|\\s|$)',
					// Names of known browsers
					names = [
						'camino', 'chrome', 'firefox', 'iceweasel', 'netscape', 'konqueror', 'lynx', 'msie', 'opera',
						'safari', 'ipod', 'iphone', 'blackberry', 'ps3', 'rekonq'
					],
					// Tanslations for conforming browser names
					nameTranslations = [],
					// Names of known layout engines
					layouts = ['gecko', 'konqueror', 'msie', 'opera', 'webkit'],
					// Translations for conforming layout names
					layoutTranslations = [ ['konqueror', 'khtml'], ['msie', 'trident'], ['opera', 'presto'] ],
					// Names of supported layout engines for version number
					layoutVersions = ['applewebkit', 'gecko'],
					// Names of known operating systems
					platforms = ['win', 'mac', 'linux', 'sunos', 'solaris', 'iphone'],
					// Translations for conforming operating system names
					platformTranslations = [ ['sunos', 'solaris'] ],

					/* Methods */

					/**
					 * Performs multiple replacements on a string
					 */
					translate = function ( source, translations ) {
						var i;
						for ( i = 0; i < translations.length; i++ ) {
							source = source.replace( translations[i][0], translations[i][1] );
						}
						return source;
					},

					/* Pre-processing */

					ua = nav.userAgent,
					match,
					name = uk,
					layout = uk,
					layoutversion = uk,
					platform = uk,
					version = x;

				if ( match = new RegExp( '(' + wildUserAgents.join( '|' ) + ')' ).exec( ua ) ) {
					// Takes a userAgent string and translates given text into something we can more easily work with
					ua = translate( ua, userAgentTranslations );
				}
				// Everything will be in lowercase from now on
				ua = ua.toLowerCase();

				/* Extraction */

				if ( match = new RegExp( '(' + names.join( '|' ) + ')' ).exec( ua ) ) {
					name = translate( match[1], nameTranslations );
				}
				if ( match = new RegExp( '(' + layouts.join( '|' ) + ')' ).exec( ua ) ) {
					layout = translate( match[1], layoutTranslations );
				}
				if ( match = new RegExp( '(' + layoutVersions.join( '|' ) + ')\\\/(\\d+)').exec( ua ) ) {
					layoutversion = parseInt( match[2], 10 );
				}
				if ( match = new RegExp( '(' + platforms.join( '|' ) + ')' ).exec( nav.platform.toLowerCase() ) ) {
					platform = translate( match[1], platformTranslations );
				}
				if ( match = new RegExp( '(' + versionPrefixes.join( '|' ) + ')' + versionSuffix ).exec( ua ) ) {
					version = match[3];
				}

				/* Edge Cases -- did I mention about how user agent string lie? */

				// Decode Safari's crazy 400+ version numbers
				if ( name.match( /safari/ ) && version > 400 ) {
					version = '2.0';
				}
				// Expose Opera 10's lies about being Opera 9.8
				if ( name === 'opera' && version >= 9.8) {
					match = ua.match( /version\/([0-9\.]*)/i );
					if ( match && match[1] ) {
						version = match[1];
					} else {
						version = '10';
					}
				}
				versionNumber = parseFloat( version, 10 ) || 0.0;

				/* Caching */

				profileCache[nav.userAgent] = {
					name: name,
					layout: layout,
					layoutVersion: layoutversion,
					platform: platform,
					version: version,
					versionBase: ( version !== x ? Math.floor( versionNumber ).toString() : x ),
					versionNumber: versionNumber
				};
			}
			return profileCache[nav.userAgent];
		},

		/**
		 * Checks the current browser against a support map object to determine if the browser has been black-listed or
		 * not. If the browser was not configured specifically it is assumed to work. It is assumed that the body
		 * element is classified as either "ltr" or "rtl". If neither is set, "ltr" is assumed.
		 *
		 * A browser map is in the following format:
		 * {
		 *   'ltr': {
		 *     // Multiple rules with configurable operators
		 *     'msie': [['>=', 7], ['!=', 9]],
		 *      // Blocked entirely
		 *     'iphone': false
		 *   },
		 *   'rtl': {
		 *     // Test against a string
		 *     'msie': [['!==', '8.1.2.3']],
		 *     // RTL rules do not fall through to LTR rules, you must explicity set each of them
		 *     'iphone': false
		 *   }
		 * }
		 *
		 * @param map {Object} Browser support map
		 * @param profile {Object} (optional) a client-profile object.
		 *
		 * @return Boolean true if browser known or assumed to be supported, false if blacklisted
		 */
		test: function ( map, profile ) {
			/*jshint evil: true */

			var conditions, dir, i, op, val;
			profile = $.isPlainObject( profile ) ? profile : $.client.profile();
			dir = $( 'body' ).is( '.rtl' ) ? 'rtl' : 'ltr';
			// Check over each browser condition to determine if we are running in a compatible client
			if ( typeof map[dir] !== 'object' || map[dir][profile.name] === undefined ) {
				// Unknown, so we assume it's working
				return true;
			}
			conditions = map[dir][profile.name];
			if ( conditions === false ) {
				return false;
			}
			for ( i = 0; i < conditions.length; i++ ) {
				op = conditions[i][0];
				val = conditions[i][1];
				if ( typeof val === 'string' ) {
					if ( !( eval( 'profile.version' + op + '"' + val + '"' ) ) ) {
						return false;
					}
				} else if ( typeof val === 'number' ) {
					if ( !( eval( 'profile.versionNumber' + op + val ) ) ) {
						return false;
					}
				}
			}

			return true;
		}
	};
}( jQuery ) );