File:FocusPlugin.js

/**
 * @module Container
 * @namespace springroll
 */
(function()
{
	var PageVisibility = include('springroll.PageVisibility');

	/**
	 * @class Container
	 */
	var plugin = new springroll.ContainerPlugin(90);

	plugin.setup = function()
	{
		// Add the default option for pauseFocusSelector
		this.options = $.extend(
			{
				pauseFocusSelector: '.pause-on-focus'
			},
			this.options);

		/**
		 * Handle the page visiblity change events, like opening a new tab
		 * or blurring the current page.
		 * @property {springroll.PageVisibility} _pageVisibility
		 * @private
		 */
		this._pageVisibility = new PageVisibility(
			onContainerFocus.bind(this),
			onContainerBlur.bind(this)
		);

		/**
		 * Whether the Game is currently "blurred" (not focused) - for pausing/unpausing
		 * @property {Boolean} _appBlurred
		 * @private
		 * @default  false
		 */
		this._appBlurred = false;

		/**
		 * Always keep the focus on the application iframe
		 * @property {Boolean} _keepFocus
		 * @private
		 * @default  false
		 */
		this._keepFocus = false;

		/**
		 * Whether the Container is currently "blurred" (not focused) - for pausing/unpausing
		 * @property {Boolean} _containerBlurred
		 * @private
		 * @default  false
		 */
		this._containerBlurred = false;

		/**
		 * Delays pausing of application to mitigate issues with asynchronous communication
		 * between Game and Container
		 * @property {int} _focusTimer
		 */
		this._focusTimer = null;

		// Focus on the window on focusing on anything else
		// without the .pause-on-focus class
		this._onDocClick = onDocClick.bind(this);
		$(document).on('focus click', this._onDocClick);

		/**
		 * Focus on the iframe's contentWindow
		 * @method focus
		 */
		this.focus = function()
		{
			this.dom.contentWindow.focus();
		};

		/**
		 * Unfocus on the iframe's contentWindow
		 * @method blur
		 */
		this.blur = function()
		{
			this.dom.contentWindow.blur();
		};

		/**
		 * Manage the focus change events sent from window and iFrame
		 * @method manageFocus
		 * @protected
		 */
		this.manageFocus = function()
		{
			// Unfocus on the iframe
			if (this._keepFocus)
			{
				this.blur();
			}

			// we only need one delayed call, at the end of any
			// sequence of rapidly-fired blur/focus events
			if (this._focusTimer)
			{
				clearTimeout(this._focusTimer);
			}

			// Delay setting of 'paused' in case we get another focus event soon.
			// Focus events are sent to the container asynchronously, and this was
			// causing rapid toggling of the pause state and related issues,
			// especially in Internet Explorer
			this._focusTimer = setTimeout(
				function()
				{
					this._focusTimer = null;
					// A manual pause cannot be overriden by focus events.
					// User must click the resume button.
					if (this._isManualPause) return;

					this.paused = this._containerBlurred && this._appBlurred;

					// Focus on the content window when blurring the app
					// but selecting the container
					if (this._keepFocus && !this._containerBlurred && this._appBlurred)
					{
						this.focus();
					}

				}.bind(this),
				100
			);
		};

		// On elements with the class name pause-on-focus
		// we will pause the game until a blur event to that item
		// has been sent
		var self = this;
		$(this.options.pauseFocusSelector).on('focus', function()
		{
			self._isManualPause = self.paused = true;
			$(this).one('blur', function()
			{
				self._isManualPause = self.paused = false;
				self.focus();
			});
		});
	};

	/**
	 * When the document is clicked
	 * @method _onDocClicked
	 * @private
	 * @param  {Event} e Click or focus event
	 */
	var onDocClick = function(e)
	{
		if (!this.loaded) return;

		var target;

		// Firefox support
		if (e.originalEvent.explicitOriginalTarget)
		{
			target = $(e.originalEvent.explicitOriginalTarget);
		}
		else
		{
			target = $(e.target);
		}
		if (!target.filter(this.options.pauseFocusSelector).length)
		{
			this.focus();
		}
	};

	/**
	 * Handle the keep focus event for the window
	 * @method onKeepFocus
	 * @private
	 */
	var onKeepFocus = function(event)
	{
		this._keepFocus = !!event.data;
		this.manageFocus();
	};

	/**
	 * Handle focus events sent from iFrame children
	 * @method onFocus
	 * @private
	 */
	var onFocus = function(e)
	{
		this._appBlurred = !e.data;
		this.manageFocus();
	};

	/**
	 * Handle focus events sent from container's window
	 * @method onContainerFocus
	 * @private
	 */
	var onContainerFocus = function(e)
	{
		this._containerBlurred = false;
		this.manageFocus();
	};

	/**
	 * Handle blur events sent from container's window
	 * @method onContainerBlur
	 * @private
	 */
	var onContainerBlur = function(e)
	{
		//Set both container and application to blurred,
		//because some blur events are only happening on the container.
		//If container is blurred because application area was just focused,
		//the application's focus event will override the blur imminently.
		this._containerBlurred = this._appBlurred = true;
		this.manageFocus();
	};

	plugin.open = function()
	{
		this.client.on(
		{
			focus: onFocus.bind(this),
			keepFocus: onKeepFocus.bind(this),
		});
	};

	plugin.opened = function()
	{
		this.focus();
	};

	plugin.close = function()
	{
		// Stop the focus timer if it's running
		if (this._focusTimer)
		{
			clearTimeout(this._focusTimer);
		}
	};

	plugin.teardown = function()
	{
		$(this.options.pauseFocusSelector).off('focus');
		$(document).off('focus click', this._onDocClick);
		delete this._onDocClick;
		if (this._pageVisibility)
		{
			this._pageVisibility.destroy();
			delete this._pageVisibility;
		}
		delete this.focus;
		delete this.blur;
		delete this.manageFocus;
		delete this._appBlurred;
		delete this._focusTimer;
		delete this._keepFocus;
		delete this._containerBlurred;
	};

}());