cashless2ecash

cashless2ecash: pay with cards for digital cash (experimental)
Log | Files | Refs | README

fastclick.js (25965B)


      1 ;(function () {
      2 	'use strict';
      3 
      4 	/**
      5 	 * @preserve FastClick: polyfill to remove click delays on browsers with touch UIs.
      6 	 *
      7 	 * @codingstandard ftlabs-jsv2
      8 	 * @copyright The Financial Times Limited [All Rights Reserved]
      9 	 * @license MIT License (see LICENSE.txt)
     10 	 */
     11 
     12 	/*jslint browser:true, node:true*/
     13 	/*global define, Event, Node*/
     14 
     15 
     16 	/**
     17 	 * Instantiate fast-clicking listeners on the specified layer.
     18 	 *
     19 	 * @constructor
     20 	 * @param {Element} layer The layer to listen on
     21 	 * @param {Object} [options={}] The options to override the defaults
     22 	 */
     23 	function FastClick(layer, options) {
     24 		var oldOnClick;
     25 
     26 		options = options || {};
     27 
     28 		/**
     29 		 * Whether a click is currently being tracked.
     30 		 *
     31 		 * @type boolean
     32 		 */
     33 		this.trackingClick = false;
     34 
     35 
     36 		/**
     37 		 * Timestamp for when click tracking started.
     38 		 *
     39 		 * @type number
     40 		 */
     41 		this.trackingClickStart = 0;
     42 
     43 
     44 		/**
     45 		 * The element being tracked for a click.
     46 		 *
     47 		 * @type EventTarget
     48 		 */
     49 		this.targetElement = null;
     50 
     51 
     52 		/**
     53 		 * X-coordinate of touch start event.
     54 		 *
     55 		 * @type number
     56 		 */
     57 		this.touchStartX = 0;
     58 
     59 
     60 		/**
     61 		 * Y-coordinate of touch start event.
     62 		 *
     63 		 * @type number
     64 		 */
     65 		this.touchStartY = 0;
     66 
     67 
     68 		/**
     69 		 * ID of the last touch, retrieved from Touch.identifier.
     70 		 *
     71 		 * @type number
     72 		 */
     73 		this.lastTouchIdentifier = 0;
     74 
     75 
     76 		/**
     77 		 * Touchmove boundary, beyond which a click will be cancelled.
     78 		 *
     79 		 * @type number
     80 		 */
     81 		this.touchBoundary = options.touchBoundary || 10;
     82 
     83 
     84 		/**
     85 		 * The FastClick layer.
     86 		 *
     87 		 * @type Element
     88 		 */
     89 		this.layer = layer;
     90 
     91 		/**
     92 		 * The minimum time between tap(touchstart and touchend) events
     93 		 *
     94 		 * @type number
     95 		 */
     96 		this.tapDelay = options.tapDelay || 200;
     97 
     98 		/**
     99 		 * The maximum time for a tap
    100 		 *
    101 		 * @type number
    102 		 */
    103 		this.tapTimeout = options.tapTimeout || 700;
    104 
    105 		if (FastClick.notNeeded(layer)) {
    106 			return;
    107 		}
    108 
    109 		// Some old versions of Android don't have Function.prototype.bind
    110 		function bind(method, context) {
    111 			return function() { return method.apply(context, arguments); };
    112 		}
    113 
    114 
    115 		var methods = ['onMouse', 'onClick', 'onTouchStart', 'onTouchMove', 'onTouchEnd', 'onTouchCancel'];
    116 		var context = this;
    117 		for (var i = 0, l = methods.length; i < l; i++) {
    118 			context[methods[i]] = bind(context[methods[i]], context);
    119 		}
    120 
    121 		// Set up event handlers as required
    122 		if (deviceIsAndroid) {
    123 			layer.addEventListener('mouseover', this.onMouse, true);
    124 			layer.addEventListener('mousedown', this.onMouse, true);
    125 			layer.addEventListener('mouseup', this.onMouse, true);
    126 		}
    127 
    128 		layer.addEventListener('click', this.onClick, true);
    129 		layer.addEventListener('touchstart', this.onTouchStart, false);
    130 		layer.addEventListener('touchmove', this.onTouchMove, false);
    131 		layer.addEventListener('touchend', this.onTouchEnd, false);
    132 		layer.addEventListener('touchcancel', this.onTouchCancel, false);
    133 
    134 		// Hack is required for browsers that don't support Event#stopImmediatePropagation (e.g. Android 2)
    135 		// which is how FastClick normally stops click events bubbling to callbacks registered on the FastClick
    136 		// layer when they are cancelled.
    137 		if (!Event.prototype.stopImmediatePropagation) {
    138 			layer.removeEventListener = function(type, callback, capture) {
    139 				var rmv = Node.prototype.removeEventListener;
    140 				if (type === 'click') {
    141 					rmv.call(layer, type, callback.hijacked || callback, capture);
    142 				} else {
    143 					rmv.call(layer, type, callback, capture);
    144 				}
    145 			};
    146 
    147 			layer.addEventListener = function(type, callback, capture) {
    148 				var adv = Node.prototype.addEventListener;
    149 				if (type === 'click') {
    150 					adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) {
    151 						if (!event.propagationStopped) {
    152 							callback(event);
    153 						}
    154 					}), capture);
    155 				} else {
    156 					adv.call(layer, type, callback, capture);
    157 				}
    158 			};
    159 		}
    160 
    161 		// If a handler is already declared in the element's onclick attribute, it will be fired before
    162 		// FastClick's onClick handler. Fix this by pulling out the user-defined handler function and
    163 		// adding it as listener.
    164 		if (typeof layer.onclick === 'function') {
    165 
    166 			// Android browser on at least 3.2 requires a new reference to the function in layer.onclick
    167 			// - the old one won't work if passed to addEventListener directly.
    168 			oldOnClick = layer.onclick;
    169 			layer.addEventListener('click', function(event) {
    170 				oldOnClick(event);
    171 			}, false);
    172 			layer.onclick = null;
    173 		}
    174 	}
    175 
    176 	/**
    177 	* Windows Phone 8.1 fakes user agent string to look like Android and iPhone.
    178 	*
    179 	* @type boolean
    180 	*/
    181 	var deviceIsWindowsPhone = navigator.userAgent.indexOf("Windows Phone") >= 0;
    182 
    183 	/**
    184 	 * Android requires exceptions.
    185 	 *
    186 	 * @type boolean
    187 	 */
    188 	var deviceIsAndroid = navigator.userAgent.indexOf('Android') > 0 && !deviceIsWindowsPhone;
    189 
    190 
    191 	/**
    192 	 * iOS requires exceptions.
    193 	 *
    194 	 * @type boolean
    195 	 */
    196 	var deviceIsIOS = /iP(ad|hone|od)/.test(navigator.userAgent) && !deviceIsWindowsPhone;
    197 
    198 
    199 	/**
    200 	 * iOS 4 requires an exception for select elements.
    201 	 *
    202 	 * @type boolean
    203 	 */
    204 	var deviceIsIOS4 = deviceIsIOS && (/OS 4_\d(_\d)?/).test(navigator.userAgent);
    205 
    206 
    207 	/**
    208 	 * iOS 6.0-7.* requires the target element to be manually derived
    209 	 *
    210 	 * @type boolean
    211 	 */
    212 	var deviceIsIOSWithBadTarget = deviceIsIOS && (/OS [6-7]_\d/).test(navigator.userAgent);
    213 
    214 	/**
    215 	 * BlackBerry requires exceptions.
    216 	 *
    217 	 * @type boolean
    218 	 */
    219 	var deviceIsBlackBerry10 = navigator.userAgent.indexOf('BB10') > 0;
    220 
    221 	/**
    222 	 * Determine whether a given element requires a native click.
    223 	 *
    224 	 * @param {EventTarget|Element} target Target DOM element
    225 	 * @returns {boolean} Returns true if the element needs a native click
    226 	 */
    227 	FastClick.prototype.needsClick = function(target) {
    228 		switch (target.nodeName.toLowerCase()) {
    229 
    230 		// Don't send a synthetic click to disabled inputs (issue #62)
    231 		case 'button':
    232 		case 'select':
    233 		case 'textarea':
    234 			if (target.disabled) {
    235 				return true;
    236 			}
    237 
    238 			break;
    239 		case 'input':
    240 
    241 			// File inputs need real clicks on iOS 6 due to a browser bug (issue #68)
    242 			if ((deviceIsIOS && target.type === 'file') || target.disabled) {
    243 				return true;
    244 			}
    245 
    246 			break;
    247 		case 'label':
    248 		case 'iframe': // iOS8 homescreen apps can prevent events bubbling into frames
    249 		case 'video':
    250 			return true;
    251 		}
    252 
    253 		return (/\bneedsclick\b/).test(target.className);
    254 	};
    255 
    256 
    257 	/**
    258 	 * Determine whether a given element requires a call to focus to simulate click into element.
    259 	 *
    260 	 * @param {EventTarget|Element} target Target DOM element
    261 	 * @returns {boolean} Returns true if the element requires a call to focus to simulate native click.
    262 	 */
    263 	FastClick.prototype.needsFocus = function(target) {
    264 		switch (target.nodeName.toLowerCase()) {
    265 		case 'textarea':
    266 			return true;
    267 		case 'select':
    268 			return !deviceIsAndroid;
    269 		case 'input':
    270 			switch (target.type) {
    271 			case 'button':
    272 			case 'checkbox':
    273 			case 'file':
    274 			case 'image':
    275 			case 'radio':
    276 			case 'submit':
    277 				return false;
    278 			}
    279 
    280 			// No point in attempting to focus disabled inputs
    281 			return !target.disabled && !target.readOnly;
    282 		default:
    283 			return (/\bneedsfocus\b/).test(target.className);
    284 		}
    285 	};
    286 
    287 
    288 	/**
    289 	 * Send a click event to the specified element.
    290 	 *
    291 	 * @param {EventTarget|Element} targetElement
    292 	 * @param {Event} event
    293 	 */
    294 	FastClick.prototype.sendClick = function(targetElement, event) {
    295 		var clickEvent, touch;
    296 
    297 		// On some Android devices activeElement needs to be blurred otherwise the synthetic click will have no effect (#24)
    298 		if (document.activeElement && document.activeElement !== targetElement) {
    299 			document.activeElement.blur();
    300 		}
    301 
    302 		touch = event.changedTouches[0];
    303 
    304 		// Synthesise a click event, with an extra attribute so it can be tracked
    305 		clickEvent = document.createEvent('MouseEvents');
    306 		clickEvent.initMouseEvent(this.determineEventType(targetElement), true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
    307 		clickEvent.forwardedTouchEvent = true;
    308 		targetElement.dispatchEvent(clickEvent);
    309 	};
    310 
    311 	FastClick.prototype.determineEventType = function(targetElement) {
    312 
    313 		//Issue #159: Android Chrome Select Box does not open with a synthetic click event
    314 		if (deviceIsAndroid && targetElement.tagName.toLowerCase() === 'select') {
    315 			return 'mousedown';
    316 		}
    317 
    318 		return 'click';
    319 	};
    320 
    321 
    322 	/**
    323 	 * @param {EventTarget|Element} targetElement
    324 	 */
    325 	FastClick.prototype.focus = function(targetElement) {
    326 		var length;
    327 
    328 		// Issue #160: on iOS 7, some input elements (e.g. date datetime month) throw a vague TypeError on setSelectionRange. These elements don't have an integer value for the selectionStart and selectionEnd properties, but unfortunately that can't be used for detection because accessing the properties also throws a TypeError. Just check the type instead. Filed as Apple bug #15122724.
    329 		if (deviceIsIOS && targetElement.setSelectionRange && targetElement.type.indexOf('date') !== 0 && targetElement.type !== 'time' && targetElement.type !== 'month') {
    330 			length = targetElement.value.length;
    331 			targetElement.setSelectionRange(length, length);
    332 		} else {
    333 			targetElement.focus();
    334 		}
    335 	};
    336 
    337 
    338 	/**
    339 	 * Check whether the given target element is a child of a scrollable layer and if so, set a flag on it.
    340 	 *
    341 	 * @param {EventTarget|Element} targetElement
    342 	 */
    343 	FastClick.prototype.updateScrollParent = function(targetElement) {
    344 		var scrollParent, parentElement;
    345 
    346 		scrollParent = targetElement.fastClickScrollParent;
    347 
    348 		// Attempt to discover whether the target element is contained within a scrollable layer. Re-check if the
    349 		// target element was moved to another parent.
    350 		if (!scrollParent || !scrollParent.contains(targetElement)) {
    351 			parentElement = targetElement;
    352 			do {
    353 				if (parentElement.scrollHeight > parentElement.offsetHeight) {
    354 					scrollParent = parentElement;
    355 					targetElement.fastClickScrollParent = parentElement;
    356 					break;
    357 				}
    358 
    359 				parentElement = parentElement.parentElement;
    360 			} while (parentElement);
    361 		}
    362 
    363 		// Always update the scroll top tracker if possible.
    364 		if (scrollParent) {
    365 			scrollParent.fastClickLastScrollTop = scrollParent.scrollTop;
    366 		}
    367 	};
    368 
    369 
    370 	/**
    371 	 * @param {EventTarget} targetElement
    372 	 * @returns {Element|EventTarget}
    373 	 */
    374 	FastClick.prototype.getTargetElementFromEventTarget = function(eventTarget) {
    375 
    376 		// On some older browsers (notably Safari on iOS 4.1 - see issue #56) the event target may be a text node.
    377 		if (eventTarget.nodeType === Node.TEXT_NODE) {
    378 			return eventTarget.parentNode;
    379 		}
    380 
    381 		return eventTarget;
    382 	};
    383 
    384 
    385 	/**
    386 	 * On touch start, record the position and scroll offset.
    387 	 *
    388 	 * @param {Event} event
    389 	 * @returns {boolean}
    390 	 */
    391 	FastClick.prototype.onTouchStart = function(event) {
    392 		var targetElement, touch, selection;
    393 
    394 		// Ignore multiple touches, otherwise pinch-to-zoom is prevented if both fingers are on the FastClick element (issue #111).
    395 		if (event.targetTouches.length > 1) {
    396 			return true;
    397 		}
    398 
    399 		targetElement = this.getTargetElementFromEventTarget(event.target);
    400 		touch = event.targetTouches[0];
    401 
    402 		if (deviceIsIOS) {
    403 
    404 			// Only trusted events will deselect text on iOS (issue #49)
    405 			selection = window.getSelection();
    406 			if (selection.rangeCount && !selection.isCollapsed) {
    407 				return true;
    408 			}
    409 
    410 			if (!deviceIsIOS4) {
    411 
    412 				// Weird things happen on iOS when an alert or confirm dialog is opened from a click event callback (issue #23):
    413 				// when the user next taps anywhere else on the page, new touchstart and touchend events are dispatched
    414 				// with the same identifier as the touch event that previously triggered the click that triggered the alert.
    415 				// Sadly, there is an issue on iOS 4 that causes some normal touch events to have the same identifier as an
    416 				// immediately preceeding touch event (issue #52), so this fix is unavailable on that platform.
    417 				// Issue 120: touch.identifier is 0 when Chrome dev tools 'Emulate touch events' is set with an iOS device UA string,
    418 				// which causes all touch events to be ignored. As this block only applies to iOS, and iOS identifiers are always long,
    419 				// random integers, it's safe to to continue if the identifier is 0 here.
    420 				if (touch.identifier && touch.identifier === this.lastTouchIdentifier) {
    421 					event.preventDefault();
    422 					return false;
    423 				}
    424 
    425 				this.lastTouchIdentifier = touch.identifier;
    426 
    427 				// If the target element is a child of a scrollable layer (using -webkit-overflow-scrolling: touch) and:
    428 				// 1) the user does a fling scroll on the scrollable layer
    429 				// 2) the user stops the fling scroll with another tap
    430 				// then the event.target of the last 'touchend' event will be the element that was under the user's finger
    431 				// when the fling scroll was started, causing FastClick to send a click event to that layer - unless a check
    432 				// is made to ensure that a parent layer was not scrolled before sending a synthetic click (issue #42).
    433 				this.updateScrollParent(targetElement);
    434 			}
    435 		}
    436 
    437 		this.trackingClick = true;
    438 		this.trackingClickStart = event.timeStamp;
    439 		this.targetElement = targetElement;
    440 
    441 		this.touchStartX = touch.pageX;
    442 		this.touchStartY = touch.pageY;
    443 
    444 		// Prevent phantom clicks on fast double-tap (issue #36)
    445 		if ((event.timeStamp - this.lastClickTime) < this.tapDelay) {
    446 			event.preventDefault();
    447 		}
    448 
    449 		return true;
    450 	};
    451 
    452 
    453 	/**
    454 	 * Based on a touchmove event object, check whether the touch has moved past a boundary since it started.
    455 	 *
    456 	 * @param {Event} event
    457 	 * @returns {boolean}
    458 	 */
    459 	FastClick.prototype.touchHasMoved = function(event) {
    460 		var touch = event.changedTouches[0], boundary = this.touchBoundary;
    461 
    462 		if (Math.abs(touch.pageX - this.touchStartX) > boundary || Math.abs(touch.pageY - this.touchStartY) > boundary) {
    463 			return true;
    464 		}
    465 
    466 		return false;
    467 	};
    468 
    469 
    470 	/**
    471 	 * Update the last position.
    472 	 *
    473 	 * @param {Event} event
    474 	 * @returns {boolean}
    475 	 */
    476 	FastClick.prototype.onTouchMove = function(event) {
    477 		if (!this.trackingClick) {
    478 			return true;
    479 		}
    480 
    481 		// If the touch has moved, cancel the click tracking
    482 		if (this.targetElement !== this.getTargetElementFromEventTarget(event.target) || this.touchHasMoved(event)) {
    483 			this.trackingClick = false;
    484 			this.targetElement = null;
    485 		}
    486 
    487 		return true;
    488 	};
    489 
    490 
    491 	/**
    492 	 * Attempt to find the labelled control for the given label element.
    493 	 *
    494 	 * @param {EventTarget|HTMLLabelElement} labelElement
    495 	 * @returns {Element|null}
    496 	 */
    497 	FastClick.prototype.findControl = function(labelElement) {
    498 
    499 		// Fast path for newer browsers supporting the HTML5 control attribute
    500 		if (labelElement.control !== undefined) {
    501 			return labelElement.control;
    502 		}
    503 
    504 		// All browsers under test that support touch events also support the HTML5 htmlFor attribute
    505 		if (labelElement.htmlFor) {
    506 			return document.getElementById(labelElement.htmlFor);
    507 		}
    508 
    509 		// If no for attribute exists, attempt to retrieve the first labellable descendant element
    510 		// the list of which is defined here: http://www.w3.org/TR/html5/forms.html#category-label
    511 		return labelElement.querySelector('button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea');
    512 	};
    513 
    514 
    515 	/**
    516 	 * On touch end, determine whether to send a click event at once.
    517 	 *
    518 	 * @param {Event} event
    519 	 * @returns {boolean}
    520 	 */
    521 	FastClick.prototype.onTouchEnd = function(event) {
    522 		var forElement, trackingClickStart, targetTagName, scrollParent, touch, targetElement = this.targetElement;
    523 
    524 		if (!this.trackingClick) {
    525 			return true;
    526 		}
    527 
    528 		// Prevent phantom clicks on fast double-tap (issue #36)
    529 		if ((event.timeStamp - this.lastClickTime) < this.tapDelay) {
    530 			this.cancelNextClick = true;
    531 			return true;
    532 		}
    533 
    534 		if ((event.timeStamp - this.trackingClickStart) > this.tapTimeout) {
    535 			return true;
    536 		}
    537 
    538 		// Reset to prevent wrong click cancel on input (issue #156).
    539 		this.cancelNextClick = false;
    540 
    541 		this.lastClickTime = event.timeStamp;
    542 
    543 		trackingClickStart = this.trackingClickStart;
    544 		this.trackingClick = false;
    545 		this.trackingClickStart = 0;
    546 
    547 		// On some iOS devices, the targetElement supplied with the event is invalid if the layer
    548 		// is performing a transition or scroll, and has to be re-detected manually. Note that
    549 		// for this to function correctly, it must be called *after* the event target is checked!
    550 		// See issue #57; also filed as rdar://13048589 .
    551 		if (deviceIsIOSWithBadTarget) {
    552 			touch = event.changedTouches[0];
    553 
    554 			// In certain cases arguments of elementFromPoint can be negative, so prevent setting targetElement to null
    555 			targetElement = document.elementFromPoint(touch.pageX - window.pageXOffset, touch.pageY - window.pageYOffset) || targetElement;
    556 			targetElement.fastClickScrollParent = this.targetElement.fastClickScrollParent;
    557 		}
    558 
    559 		targetTagName = targetElement.tagName.toLowerCase();
    560 		if (targetTagName === 'label') {
    561 			forElement = this.findControl(targetElement);
    562 			if (forElement) {
    563 				this.focus(targetElement);
    564 				if (deviceIsAndroid) {
    565 					return false;
    566 				}
    567 
    568 				targetElement = forElement;
    569 			}
    570 		} else if (this.needsFocus(targetElement)) {
    571 
    572 			// Case 1: If the touch started a while ago (best guess is 100ms based on tests for issue #36) then focus will be triggered anyway. Return early and unset the target element reference so that the subsequent click will be allowed through.
    573 			// Case 2: Without this exception for input elements tapped when the document is contained in an iframe, then any inputted text won't be visible even though the value attribute is updated as the user types (issue #37).
    574 			if ((event.timeStamp - trackingClickStart) > 100 || (deviceIsIOS && window.top !== window && targetTagName === 'input')) {
    575 				this.targetElement = null;
    576 				return false;
    577 			}
    578 
    579 			this.focus(targetElement);
    580 			this.sendClick(targetElement, event);
    581 
    582 			// Select elements need the event to go through on iOS 4, otherwise the selector menu won't open.
    583 			// Also this breaks opening selects when VoiceOver is active on iOS6, iOS7 (and possibly others)
    584 			if (!deviceIsIOS || targetTagName !== 'select') {
    585 				this.targetElement = null;
    586 				event.preventDefault();
    587 			}
    588 
    589 			return false;
    590 		}
    591 
    592 		if (deviceIsIOS && !deviceIsIOS4) {
    593 
    594 			// Don't send a synthetic click event if the target element is contained within a parent layer that was scrolled
    595 			// and this tap is being used to stop the scrolling (usually initiated by a fling - issue #42).
    596 			scrollParent = targetElement.fastClickScrollParent;
    597 			if (scrollParent && scrollParent.fastClickLastScrollTop !== scrollParent.scrollTop) {
    598 				return true;
    599 			}
    600 		}
    601 
    602 		// Prevent the actual click from going though - unless the target node is marked as requiring
    603 		// real clicks or if it is in the whitelist in which case only non-programmatic clicks are permitted.
    604 		if (!this.needsClick(targetElement)) {
    605 			event.preventDefault();
    606 			this.sendClick(targetElement, event);
    607 		}
    608 
    609 		return false;
    610 	};
    611 
    612 
    613 	/**
    614 	 * On touch cancel, stop tracking the click.
    615 	 *
    616 	 * @returns {void}
    617 	 */
    618 	FastClick.prototype.onTouchCancel = function() {
    619 		this.trackingClick = false;
    620 		this.targetElement = null;
    621 	};
    622 
    623 
    624 	/**
    625 	 * Determine mouse events which should be permitted.
    626 	 *
    627 	 * @param {Event} event
    628 	 * @returns {boolean}
    629 	 */
    630 	FastClick.prototype.onMouse = function(event) {
    631 
    632 		// If a target element was never set (because a touch event was never fired) allow the event
    633 		if (!this.targetElement) {
    634 			return true;
    635 		}
    636 
    637 		if (event.forwardedTouchEvent) {
    638 			return true;
    639 		}
    640 
    641 		// Programmatically generated events targeting a specific element should be permitted
    642 		if (!event.cancelable) {
    643 			return true;
    644 		}
    645 
    646 		// Derive and check the target element to see whether the mouse event needs to be permitted;
    647 		// unless explicitly enabled, prevent non-touch click events from triggering actions,
    648 		// to prevent ghost/doubleclicks.
    649 		if (!this.needsClick(this.targetElement) || this.cancelNextClick) {
    650 
    651 			// Prevent any user-added listeners declared on FastClick element from being fired.
    652 			if (event.stopImmediatePropagation) {
    653 				event.stopImmediatePropagation();
    654 			} else {
    655 
    656 				// Part of the hack for browsers that don't support Event#stopImmediatePropagation (e.g. Android 2)
    657 				event.propagationStopped = true;
    658 			}
    659 
    660 			// Cancel the event
    661 			event.stopPropagation();
    662 			event.preventDefault();
    663 
    664 			return false;
    665 		}
    666 
    667 		// If the mouse event is permitted, return true for the action to go through.
    668 		return true;
    669 	};
    670 
    671 
    672 	/**
    673 	 * On actual clicks, determine whether this is a touch-generated click, a click action occurring
    674 	 * naturally after a delay after a touch (which needs to be cancelled to avoid duplication), or
    675 	 * an actual click which should be permitted.
    676 	 *
    677 	 * @param {Event} event
    678 	 * @returns {boolean}
    679 	 */
    680 	FastClick.prototype.onClick = function(event) {
    681 		var permitted;
    682 
    683 		// It's possible for another FastClick-like library delivered with third-party code to fire a click event before FastClick does (issue #44). In that case, set the click-tracking flag back to false and return early. This will cause onTouchEnd to return early.
    684 		if (this.trackingClick) {
    685 			this.targetElement = null;
    686 			this.trackingClick = false;
    687 			return true;
    688 		}
    689 
    690 		// Very odd behaviour on iOS (issue #18): if a submit element is present inside a form and the user hits enter in the iOS simulator or clicks the Go button on the pop-up OS keyboard the a kind of 'fake' click event will be triggered with the submit-type input element as the target.
    691 		if (event.target.type === 'submit' && event.detail === 0) {
    692 			return true;
    693 		}
    694 
    695 		permitted = this.onMouse(event);
    696 
    697 		// Only unset targetElement if the click is not permitted. This will ensure that the check for !targetElement in onMouse fails and the browser's click doesn't go through.
    698 		if (!permitted) {
    699 			this.targetElement = null;
    700 		}
    701 
    702 		// If clicks are permitted, return true for the action to go through.
    703 		return permitted;
    704 	};
    705 
    706 
    707 	/**
    708 	 * Remove all FastClick's event listeners.
    709 	 *
    710 	 * @returns {void}
    711 	 */
    712 	FastClick.prototype.destroy = function() {
    713 		var layer = this.layer;
    714 
    715 		if (deviceIsAndroid) {
    716 			layer.removeEventListener('mouseover', this.onMouse, true);
    717 			layer.removeEventListener('mousedown', this.onMouse, true);
    718 			layer.removeEventListener('mouseup', this.onMouse, true);
    719 		}
    720 
    721 		layer.removeEventListener('click', this.onClick, true);
    722 		layer.removeEventListener('touchstart', this.onTouchStart, false);
    723 		layer.removeEventListener('touchmove', this.onTouchMove, false);
    724 		layer.removeEventListener('touchend', this.onTouchEnd, false);
    725 		layer.removeEventListener('touchcancel', this.onTouchCancel, false);
    726 	};
    727 
    728 
    729 	/**
    730 	 * Check whether FastClick is needed.
    731 	 *
    732 	 * @param {Element} layer The layer to listen on
    733 	 */
    734 	FastClick.notNeeded = function(layer) {
    735 		var metaViewport;
    736 		var chromeVersion;
    737 		var blackberryVersion;
    738 		var firefoxVersion;
    739 
    740 		// Devices that don't support touch don't need FastClick
    741 		if (typeof window.ontouchstart === 'undefined') {
    742 			return true;
    743 		}
    744 
    745 		// Chrome version - zero for other browsers
    746 		chromeVersion = +(/Chrome\/([0-9]+)/.exec(navigator.userAgent) || [,0])[1];
    747 
    748 		if (chromeVersion) {
    749 
    750 			if (deviceIsAndroid) {
    751 				metaViewport = document.querySelector('meta[name=viewport]');
    752 
    753 				if (metaViewport) {
    754 					// Chrome on Android with user-scalable="no" doesn't need FastClick (issue #89)
    755 					if (metaViewport.content.indexOf('user-scalable=no') !== -1) {
    756 						return true;
    757 					}
    758 					// Chrome 32 and above with width=device-width or less don't need FastClick
    759 					if (chromeVersion > 31 && document.documentElement.scrollWidth <= window.outerWidth) {
    760 						return true;
    761 					}
    762 				}
    763 
    764 			// Chrome desktop doesn't need FastClick (issue #15)
    765 			} else {
    766 				return true;
    767 			}
    768 		}
    769 
    770 		if (deviceIsBlackBerry10) {
    771 			blackberryVersion = navigator.userAgent.match(/Version\/([0-9]*)\.([0-9]*)/);
    772 
    773 			// BlackBerry 10.3+ does not require Fastclick library.
    774 			// https://github.com/ftlabs/fastclick/issues/251
    775 			if (blackberryVersion[1] >= 10 && blackberryVersion[2] >= 3) {
    776 				metaViewport = document.querySelector('meta[name=viewport]');
    777 
    778 				if (metaViewport) {
    779 					// user-scalable=no eliminates click delay.
    780 					if (metaViewport.content.indexOf('user-scalable=no') !== -1) {
    781 						return true;
    782 					}
    783 					// width=device-width (or less than device-width) eliminates click delay.
    784 					if (document.documentElement.scrollWidth <= window.outerWidth) {
    785 						return true;
    786 					}
    787 				}
    788 			}
    789 		}
    790 
    791 		// IE10 with -ms-touch-action: none or manipulation, which disables double-tap-to-zoom (issue #97)
    792 		if (layer.style.msTouchAction === 'none' || layer.style.touchAction === 'manipulation') {
    793 			return true;
    794 		}
    795 
    796 		// Firefox version - zero for other browsers
    797 		firefoxVersion = +(/Firefox\/([0-9]+)/.exec(navigator.userAgent) || [,0])[1];
    798 
    799 		if (firefoxVersion >= 27) {
    800 			// Firefox 27+ does not have tap delay if the content is not zoomable - https://bugzilla.mozilla.org/show_bug.cgi?id=922896
    801 
    802 			metaViewport = document.querySelector('meta[name=viewport]');
    803 			if (metaViewport && (metaViewport.content.indexOf('user-scalable=no') !== -1 || document.documentElement.scrollWidth <= window.outerWidth)) {
    804 				return true;
    805 			}
    806 		}
    807 
    808 		// IE11: prefixed -ms-touch-action is no longer supported and it's recomended to use non-prefixed version
    809 		// http://msdn.microsoft.com/en-us/library/windows/apps/Hh767313.aspx
    810 		if (layer.style.touchAction === 'none' || layer.style.touchAction === 'manipulation') {
    811 			return true;
    812 		}
    813 
    814 		return false;
    815 	};
    816 
    817 
    818 	/**
    819 	 * Factory method for creating a FastClick object
    820 	 *
    821 	 * @param {Element} layer The layer to listen on
    822 	 * @param {Object} [options={}] The options to override the defaults
    823 	 */
    824 	FastClick.attach = function(layer, options) {
    825 		return new FastClick(layer, options);
    826 	};
    827 
    828 
    829 	if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) {
    830 
    831 		// AMD. Register as an anonymous module.
    832 		define(function() {
    833 			return FastClick;
    834 		});
    835 	} else if (typeof module !== 'undefined' && module.exports) {
    836 		module.exports = FastClick.attach;
    837 		module.exports.FastClick = FastClick;
    838 	} else {
    839 		window.FastClick = FastClick;
    840 	}
    841 }());