All files Browser.js

79.07% Statements 34/43
70% Branches 21/30
84.62% Functions 11/13
79.07% Lines 34/43

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  1x                           5x 18x   18x 18x     18x   5x 5x                   13x 13x 13x     1x               13x 13x     13x                               13x 13x   13x 8x         4x   2x   2x         5x                 1x 1x                     1x   1x                         2x         4x     2x                     1x                 1x               1x                 1x  
var
	util = require( './util' ),
	browser;
 
/**
 * Memoize a class method. Caches the result of the method based on the
 * arguments. Instances do not share a cache.
 * @param {Function} method Method to be memoized
 * @return {Function}
 */
function memoize( method ) {
	/**
	 * Memoized version of the method
	 * @return {Function}
	 */
	var memoized = function () {
		var cache = this[ '__cache' + memoized.cacheId ] ||
			( this[ '__cache' + memoized.cacheId ] = {} ),
			key = [].join.call( arguments, '|' );
		Iif ( Object.prototype.hasOwnProperty.call( cache, key ) ) {
			return cache[ key ];
		}
		return ( cache[ key ] = method.apply( this, arguments ) );
	};
	memoized.cacheId = Date.now().toString() + Math.random().toString();
	return memoized;
}
 
/**
 * Representation of user's current browser
 * @class Browser
 * @param {string} ua the user agent of the current browser
 * @param {jQuery.Object} $container an element to associate with the Browser object
 */
function Browser( ua, $container ) {
	this.userAgent = ua;
	this.$el = $container;
	this._fixIosLandscapeBug();
}
 
Browser.prototype = {
	/**
	 * When rotating to landscape stop page zooming on ios 4 and 5.
	 * @memberof Browser
	 * @instance
	 * @private
	 */
	_fixIosLandscapeBug: function () {
		var self = this,
			viewport = this.$el.find( 'meta[name="viewport"]' )[0];
 
		// see http://adactio.com/journal/4470/ (fixed in ios 6)
		Iif ( viewport && ( this.isIos( 4 ) || this.isIos( 5 ) ) ) {
			this.lockViewport();
			document.addEventListener( 'gesturestart', function () {
				self.lockViewport();
			}, false );
		}
	},
	/**
	 * Returns whether the current browser is an ios device.
	 * FIXME: jquery.client does not support iPad detection so we cannot use it.
	 * @memberof Browser
	 * @instance
	 * @param {number} [version] integer describing a specific version you want to test against.
	 * @return {boolean}
	 */
	isIos: memoize( function ( version ) {
		var ua = this.userAgent,
			ios = /ipad|iphone|ipod/i.test( ua );
 
		if ( ios && version ) {
			switch ( version ) {
				case 8:
					// Test UA for iOS8. Or for simulator look for Version 8
					// In the iOS simulator the OS is the host machine OS version
					// This makes testing in iOS8 simulator work as expected
					return /OS 8_/.test( ua ) || /Version\/8/.test( ua );
				case 4:
					return /OS 4_/.test( ua );
				case 5:
					return /OS 5_/.test( ua );
				default:
					return false;
			}
		} else {
			return ios;
		}
	} ),
	/**
	 * Locks the viewport so that pinch zooming is disabled
	 * @memberof Browser
	 * @instance
	 */
	lockViewport: function () {
		Eif ( this.$el ) {
			this.$el.find( 'meta[name="viewport"]' )
				.attr( 'content', 'initial-scale=1.0, maximum-scale=1.0, user-scalable=no' );
		}
	},
	/**
	 * Determine if a device has a widescreen.
	 * @memberof Browser
	 * @instance
	 * @return {boolean}
	 */
	isWideScreen: memoize( function () {
		var val = parseInt( mw.config.get( 'wgMFDeviceWidthTablet' ), 10 );
		// Check portrait and landscape mode to be consistent
		return window.innerWidth >= val || window.innerHeight >= val;
	} ),
	/**
	 * Checks browser support for CSS transforms, transitions
	 * and CSS animation.
	 * Currently assumes support for the latter 2 in the case of the
	 * former.
	 * See http://stackoverflow.com/a/12621264/365238
	 * @memberof Browser
	 * @instance
	 * @return {boolean}
	 */
	supportsAnimations: memoize( function () {
		var elemStyle = document.createElement( 'foo' ).style;
		function supportsProperty( property ) {
			// We only test "webkit-", because that's the only prefix needed for the relevant
			// properties (in supportsAnimations) and supported browsers. If usage is expanded,
			// other prefixes may need to be checked as well.
			return property in elemStyle ||
				( 'webkit' + property[ 0 ].toUpperCase() + property.slice( 1 ) ) in elemStyle;
		}
		return supportsProperty( 'animationName' ) &&
			supportsProperty( 'transform' ) &&
			supportsProperty( 'transition' );
	} ),
	/**
	 * Whether touchstart and other touch events are supported by the current browser.
	 * @memberof Browser
	 * @instance
	 * @return {boolean}
	 */
	supportsTouchEvents: memoize( function () {
		return 'ontouchstart' in window;
	} ),
	/**
	 * Detect if browser supports geolocation
	 * @memberof Browser
	 * @instance
	 * @return {boolean}
	 */
	supportsGeoLocation: memoize( function () {
		return 'geolocation' in window.navigator;
	} )
};
 
/**
 * @memberof Browser
 * @return {Browser}
 */
Browser.getSingleton = function () {
	var $html;
	if ( !browser ) {
		$html = util.getDocument();
		browser = new Browser( window.navigator.userAgent, $html );
	}
	return browser;
};
 
module.exports = Browser;