All files jquery.client.js

95.91% Statements 94/98
88.31% Branches 68/77
100% Functions 4/4
95.83% Lines 92/96

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345                            1x                                                                                                                                 1x   1x                                         50x 3x       50x 4x       46x         46x   46x   46x                   46x     46x   46x   46x   46x   46x   46x   46x       46x   46x 46x 46x 46x 46x       46x 138x     46x       46x 40x   46x 46x   46x 41x   46x 37x   46x 40x           46x 1x     46x 2x 2x 1x   1x       46x 3x 3x 3x             46x 2x 2x 2x 2x     46x 2x 2x 2x       46x     46x                   46x                                                                                       8x 8x 3x 3x       8x   2x   6x 6x   1x   5x   1x   4x 4x 4x 4x     4x 4x   4x     4x 2x     4x 4x 4x 1x 1x 3x 3x 3x         4x 1x                   3x        
/*!
 * jQuery Client 3.0.0
 * https://gerrit.wikimedia.org/g/jquery-client/
 *
 * Copyright Trevor Parscal, Timo Tijhof, and Roan Kattouw.
 * Released under the MIT license.
 */
 
/**
 * User-agent detection
 *
 * @class jQuery.client
 * @hideconstructor
 */
( function () {
	/**
	 * @typedef {Object} jQuery.client.Profile An object containing information about the client.
	 *
	 * @property {string|'unknown'} name Name of the browser. Recognized browser names:
	 * - `android` (legacy Android browser, prior to Chrome Mobile)
	 * - `chrome` (includes Chrome Mobile, Microsoft Edge, Opera, and others)
	 * - `crios` (Chrome on iOS, which uses Mobile Safari)
	 * - `edge` (legacy Microsoft Edge, which uses EdgeHTML)
	 * - `firefox` (includes Firefox Mobile, Iceweasel, and others)
	 * - `fxios` (Firefox on iOS, which uses Mobile Safari)
	 * - `konqueror`
	 * - `msie`
	 * - `opera` (legacy Opera, which uses Presto)
	 * - `rekonq`
	 * - `safari` (including Mobile Safari)
	 * - `silk`
	 *
	 * @property {string|'unknown'} layout Name of the layout engine. Recognised layout engines:
	 *
	 * - `edge` (EdgeHTML 12-18, as used by legacy Microsoft Edge)
	 * - `gecko`
	 * - `khtml`
	 * - `presto`
	 * - `trident`
	 * - `webkit`
	 *
	 * Note that Chrome and Chromium-based browsers like Opera have their layout
	 * engine identified as `webkit`.
	 *
	 * @property {number|'unknown'} layoutVersion Version of the layout engine,
	 * e.g. `6` or `20101026`.
	 *
	 * @property {string|'unknown'} platform Operating system the browser is running on.
	 * Recognised platforms:
	 *
	 * - `ipad`
	 * - `iphone`
	 * - `linux`
	 * - `mac`
	 * - `solaris` (untested)
	 * - `win`
	 *
	 * @property {string|'unknown'} version
	 * @property {string|'unknown'} versionBase
	 * @property {number} versionNumber
	 *
	 * @example
	 * {
	 *     'name': 'firefox',
	 *     'layout': 'gecko',
	 *     'layoutVersion': 20101026,
	 *     'platform': 'linux'
	 *     'version': '3.5.1',
	 *     'versionBase': '3',
	 *     'versionNumber': 3.5,
	 * }
	 */
 
	/**
	 * Keyed by the user agent string.
	 *
	 * @private
	 * @type {Object.<string,jQuery.client.Profile>}
	 */
	const profileCache = {};
 
	$.client = {
 
		/**
		 * Get an object containing information about the client.
		 *
		 * @example
		 * if ( $.client.profile().layout == 'gecko' ) {
		 *     // This will only run in Gecko browsers, such as Mozilla Firefox.
		 * }
		 *
		 * var profile = $.client.profile();
		 * if ( profile.layout == 'gecko' && profile.platform == 'linux' ) {
		 *     // This will only run in Gecko browsers on Linux.
		 * }
		 *
		 * @memberof jQuery.client
		 * @param {{userAgent: string, platform: string}} [nav=window.navigator] An object with
		 *  a `userAgent` and `platform` property.
		 * @return {jQuery.client.Profile}
		 */
		profile: function ( nav ) {
			if ( !nav ) {
				nav = window.navigator;
			}
 
			// Use the cached version if possible
			if ( profileCache[ nav.userAgent + '|' + nav.platform ] ) {
				return profileCache[ nav.userAgent + '|' + nav.platform ];
			}
 
			const
				key = nav.userAgent + '|' + nav.platform,
 
				// Configuration
 
				// Name of browsers or layout engines we don't recognize
				uk = 'unknown',
				// Generic version digit
				x = 'x',
				// Fixups for user agent strings that contain wild words
				wildFixups = [
					// Chrome lives in the shadow of Safari still
					[ 'Chrome Safari', 'Chrome' ],
					// KHTML is the layout engine not the browser - LIES!
					[ 'KHTML/', 'Konqueror/' ],
					// For Firefox Mobile, strip out "Android;" or "Android [version]" so that we
					// classify it as Firefox instead of Android (default browser)
					[ /Android(?:;|\s[a-zA-Z0-9.+-]+)(.*Firefox)/, '$1' ]
				],
				// Strings which precede a version number in a user agent string
				versionPrefixes = '(?:chrome|crios|firefox|fxios|opera|version|konqueror|msie|safari|android)',
				// This matches the actual version number, with non-capturing groups for the
				// separator and suffix
				versionSuffix = '(?:\\/|;?\\s|)([a-z0-9\\.\\+]*?)(?:;|dev|rel|\\)|\\s|$)',
				// Match the names of known browser families
				rName = /(chrome|crios|firefox|fxios|konqueror|msie|opera|safari|rekonq|android)/,
				// Match the name of known layout engines
				rLayout = /(gecko|konqueror|msie|trident|edge|opera|webkit)/,
				// Translations for conforming layout names
				layoutMap = { konqueror: 'khtml', msie: 'trident', opera: 'presto' },
				// Match the prefix and version of supported layout engines
				rLayoutVersion = /(applewebkit|gecko|trident|edge)\/(\d+)/,
				// Match the name of known operating systems
				rPlatform = /(win|wow64|mac|linux|sunos|solaris|iphone|ipad)/,
				// Translations for conforming operating system names
				platformMap = { sunos: 'solaris', wow64: 'win' };
 
			// Pre-processing
			let
				ua = nav.userAgent,
				match,
				name = uk,
				layout = uk,
				layoutversion = uk,
				platform = uk,
				version = x;
 
			// Takes a userAgent string and fixes it into something we can more
			// easily work with
			wildFixups.forEach( ( fixup ) => {
				ua = ua.replace( fixup[ 0 ], fixup[ 1 ] );
			} );
			// Everything will be in lowercase from now on
			ua = ua.toLowerCase();
 
			// Extraction
 
			if ( ( match = rName.exec( ua ) ) ) {
				name = match[ 1 ];
			}
			Eif ( ( match = rLayout.exec( ua ) ) ) {
				layout = layoutMap[ match[ 1 ] ] || match[ 1 ];
			}
			if ( ( match = rLayoutVersion.exec( ua ) ) ) {
				layoutversion = parseInt( match[ 2 ], 10 );
			}
			if ( ( match = rPlatform.exec( nav.platform.toLowerCase() ) ) ) {
				platform = platformMap[ match[ 1 ] ] || match[ 1 ];
			}
			if ( ( match = new RegExp( versionPrefixes + versionSuffix ).exec( ua ) ) ) {
				version = match[ 1 ];
			}
 
			// Edge Cases -- did I mention about how user agent string lie?
 
			// Decode Safari's crazy 400+ version numbers
			if ( name === '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( /\bversion\/([0-9.]*)/ );
				if ( match && match[ 1 ] ) {
					version = match[ 1 ];
				} else {
					version = '10';
				}
			}
			// And IE 11's lies about being not being IE
			if ( layout === 'trident' && layoutversion >= 7 && ( match = ua.match( /\brv[ :/]([0-9.]*)/ ) ) ) {
				Eif ( match[ 1 ] ) {
					name = 'msie';
					version = match[ 1 ];
				}
			}
			// And MS Edge's lies about being Chrome
			//
			// It's different enough from classic IE Trident engine that they do this
			// to avoid getting caught by MSIE-specific browser sniffing.
			if ( name === 'chrome' && ( match = ua.match( /\bedge\/([0-9.]*)/ ) ) ) {
				name = 'edge';
				version = match[ 1 ];
				layout = 'edge';
				layoutversion = parseInt( match[ 1 ], 10 );
			}
			// And Amazon Silk's lies about being Android on mobile or Safari on desktop
			if ( ( match = ua.match( /\bsilk\/([0-9.\-_]*)/ ) ) ) {
				Eif ( match[ 1 ] ) {
					name = 'silk';
					version = match[ 1 ];
				}
			}
 
			const versionNumber = parseFloat( version, 10 ) || 0.0;
 
			// Caching
			profileCache[ key ] = {
				name: name,
				layout: layout,
				layoutVersion: layoutversion,
				platform: platform,
				version: version,
				versionBase: ( version !== x ? Math.floor( versionNumber ).toString() : x ),
				versionNumber: versionNumber
			};
 
			return profileCache[ key ];
		},
 
		/**
		 * Checks the current browser against a support map object.
		 *
		 * Version numbers passed as numeric values will be compared like numbers (1.2 > 1.11).
		 * Version numbers passed as string values will be compared using a simple component-wise
		 * algorithm, similar to PHP's version_compare ('1.2' < '1.11').
		 *
		 * A browser map is in the following format:
		 *
		 *     {
		 *         // Multiple rules with configurable operators
		 *         'msie': [['>=', 7], ['!=', 9]],
		 *         // Match no versions
		 *         'iphone': false,
		 *         // Match any version
		 *         'android': null
		 *     }
		 *
		 * It can optionally be split into ltr/rtl sections:
		 *
		 *     {
		 *         'ltr': {
		 *             'android': null,
		 *             'iphone': false
		 *         },
		 *         'rtl': {
		 *             'android': false,
		 *             // rules are not inherited from ltr
		 *             'iphone': false
		 *         }
		 *     }
		 *
		 * @memberof jQuery.client
		 * @param {Object} map Browser support map
		 * @param {jQuery.client.Profile} [profile] A client-profile object
		 * @param {boolean} [exactMatchOnly=false] Only return true if the browser is matched,
		 *  otherwise returns true if the browser is not found.
		 *
		 * @return {boolean} The current browser is in the support map
		 */
		test: function ( map, profile, exactMatchOnly ) {
			profile = $.isPlainObject( profile ) ? profile : $.client.profile();
			if ( map.ltr && map.rtl ) {
				const dir = $( document.body ).is( '.rtl' ) ? 'rtl' : 'ltr';
				map = map[ dir ];
			}
			// Check over each browser condition to determine if we are running in a
			// compatible client
			if ( typeof map !== 'object' || map[ profile.name ] === undefined ) {
				// Not found, return true if exactMatchOnly not set, false otherwise
				return !exactMatchOnly;
			}
			const conditions = map[ profile.name ];
			if ( conditions === false ) {
				// Match no versions
				return false;
			}
			if ( conditions === null ) {
				// Match all versions
				return true;
			}
			for ( let i = 0; i < conditions.length; i++ ) {
				const op = conditions[ i ][ 0 ];
				const val = conditions[ i ][ 1 ];
				if ( typeof val === 'string' ) {
					// Perform a component-wise comparison of versions, similar to
					// PHP's version_compare but simpler. '1.11' is larger than '1.2'.
					const pieceVersion = profile.version.toString().split( '.' );
					const pieceVal = val.split( '.' );
					// Extend with zeroes to equal length
					while ( pieceVersion.length < pieceVal.length ) {
						pieceVersion.push( '0' );
					}
					while ( pieceVal.length < pieceVersion.length ) {
						pieceVal.push( '0' );
					}
					// Compare components
					let compare = 0;
					for ( let j = 0; j < pieceVersion.length; j++ ) {
						if ( Number( pieceVersion[ j ] ) < Number( pieceVal[ j ] ) ) {
							compare = -1;
							break;
						} else Eif ( Number( pieceVersion[ j ] ) > Number( pieceVal[ j ] ) ) {
							compare = 1;
							break;
						}
					}
					// compare will be -1, 0 or 1, depending on comparison result
					// eslint-disable-next-line no-eval, security/detect-eval-with-expression
					if ( !( eval( String( compare + op + '0' ) ) ) ) {
						return false;
					}
				} else Eif ( typeof val === 'number' ) {
					// eslint-disable-next-line no-eval, security/detect-eval-with-expression
					if ( !( eval( 'profile.versionNumber' + op + val ) ) ) {
						return false;
					}
				}
			}
 
			return true;
		}
	};
}() );