// Copyright 2007 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * @fileoverview Idle Timer. * * Keeps track of transitions between active and idle. This class is built on * top of ActivityMonitor. Whenever an active user becomes idle, this class * dispatches a BECOME_IDLE event. Whenever an idle user becomes active, this * class dispatches a BECOME_ACTIVE event. The amount of inactive time it * takes for a user to be considered idle is specified by the client, and * different instances of this class can all use different thresholds. * */ goog.provide('goog.ui.IdleTimer'); goog.require('goog.Timer'); goog.require('goog.events'); goog.require('goog.events.EventTarget'); goog.require('goog.structs.Set'); goog.require('goog.ui.ActivityMonitor'); /** * Event target that will give notification of state changes between active and * idle. This class is designed to require few resources while the user is * active. * @param {number} idleThreshold Amount of time in ms at which we consider the * user has gone idle. * @param {goog.ui.ActivityMonitor=} opt_activityMonitor The activity monitor * keeping track of user interaction. Defaults to a default-constructed * activity monitor. If a default activity monitor is used then this class * will dispose of it. If an activity monitor is passed in then the caller * remains responsible for disposing of it. * @constructor * @extends {goog.events.EventTarget} * @final */ goog.ui.IdleTimer = function(idleThreshold, opt_activityMonitor) { goog.events.EventTarget.call(this); var activityMonitor = opt_activityMonitor || this.getDefaultActivityMonitor_(); /** * The amount of time in ms at which we consider the user has gone idle * @type {number} * @private */ this.idleThreshold_ = idleThreshold; /** * The activity monitor keeping track of user interaction * @type {goog.ui.ActivityMonitor} * @private */ this.activityMonitor_ = activityMonitor; /** * Cached onActivityTick_ bound to the object for later use * @type {Function} * @private */ this.boundOnActivityTick_ = goog.bind(this.onActivityTick_, this); // Decide whether the user is currently active or idle. This method will // check whether it is correct to start with the user in the active state. this.maybeStillActive_(); }; goog.inherits(goog.ui.IdleTimer, goog.events.EventTarget); /** * Whether a listener is currently registered for an idle timer event. On * initialization, the user is assumed to be active. * @type {boolean} * @private */ goog.ui.IdleTimer.prototype.hasActivityListener_ = false; /** * Handle to the timer ID used for checking ongoing activity, or null * @type {?number} * @private */ goog.ui.IdleTimer.prototype.onActivityTimerId_ = null; /** * Whether the user is currently idle * @type {boolean} * @private */ goog.ui.IdleTimer.prototype.isIdle_ = false; /** * The default activity monitor created by this class, if any * @type {goog.ui.ActivityMonitor?} * @private */ goog.ui.IdleTimer.defaultActivityMonitor_ = null; /** * The idle timers that currently reference the default activity monitor * @type {goog.structs.Set} * @private */ goog.ui.IdleTimer.defaultActivityMonitorReferences_ = new goog.structs.Set(); /** * Event constants for the idle timer event target * @enum {string} */ goog.ui.IdleTimer.Event = { /** Event fired when an idle user transitions into the active state */ BECOME_ACTIVE: 'active', /** Event fired when an active user transitions into the idle state */ BECOME_IDLE: 'idle' }; /** * Gets the default activity monitor used by this class. If a default has not * been created yet, then a new one will be created. * @return {!goog.ui.ActivityMonitor} The default activity monitor. * @private */ goog.ui.IdleTimer.prototype.getDefaultActivityMonitor_ = function() { goog.ui.IdleTimer.defaultActivityMonitorReferences_.add(this); if (goog.ui.IdleTimer.defaultActivityMonitor_ == null) { goog.ui.IdleTimer.defaultActivityMonitor_ = new goog.ui.ActivityMonitor(); } return goog.ui.IdleTimer.defaultActivityMonitor_; }; /** * Removes the reference to the default activity monitor. If there are no more * references then the default activity monitor gets disposed. * @private */ goog.ui.IdleTimer.prototype.maybeDisposeDefaultActivityMonitor_ = function() { goog.ui.IdleTimer.defaultActivityMonitorReferences_.remove(this); if (goog.ui.IdleTimer.defaultActivityMonitor_ != null && goog.ui.IdleTimer.defaultActivityMonitorReferences_.isEmpty()) { goog.ui.IdleTimer.defaultActivityMonitor_.dispose(); goog.ui.IdleTimer.defaultActivityMonitor_ = null; } }; /** * Checks whether the user is active. If the user is still active, then a timer * is started to check again later. * @private */ goog.ui.IdleTimer.prototype.maybeStillActive_ = function() { // See how long before the user would go idle. The user is considered idle // after the idle time has passed, not exactly when the idle time arrives. var remainingIdleThreshold = this.idleThreshold_ + 1 - (goog.now() - this.activityMonitor_.getLastEventTime()); if (remainingIdleThreshold > 0) { // The user is still active. Check again later. this.onActivityTimerId_ = goog.Timer.callOnce(this.boundOnActivityTick_, remainingIdleThreshold); } else { // The user has not been active recently. this.becomeIdle_(); } }; /** * Handler for the timeout used for checking ongoing activity * @private */ goog.ui.IdleTimer.prototype.onActivityTick_ = function() { // The timer has fired. this.onActivityTimerId_ = null; // The maybeStillActive method will restart the timer, if appropriate. this.maybeStillActive_(); }; /** * Transitions from the active state to the idle state * @private */ goog.ui.IdleTimer.prototype.becomeIdle_ = function() { this.isIdle_ = true; // The idle timer will send notification when the user does something // interactive. goog.events.listen( this.activityMonitor_, goog.ui.ActivityMonitor.Event.ACTIVITY, this.onActivity_, false, this); this.hasActivityListener_ = true; // Notify clients of the state change. this.dispatchEvent(goog.ui.IdleTimer.Event.BECOME_IDLE); }; /** * Handler for idle timer events when the user does something interactive * @param {goog.events.Event} e The event object. * @private */ goog.ui.IdleTimer.prototype.onActivity_ = function(e) { this.becomeActive_(); }; /** * Transitions from the idle state to the active state * @private */ goog.ui.IdleTimer.prototype.becomeActive_ = function() { this.isIdle_ = false; // Stop listening to every interactive event. this.removeActivityListener_(); // Notify clients of the state change. this.dispatchEvent(goog.ui.IdleTimer.Event.BECOME_ACTIVE); // Periodically check whether the user has gone inactive. this.maybeStillActive_(); }; /** * Removes the activity listener, if necessary * @private */ goog.ui.IdleTimer.prototype.removeActivityListener_ = function() { if (this.hasActivityListener_) { goog.events.unlisten( this.activityMonitor_, goog.ui.ActivityMonitor.Event.ACTIVITY, this.onActivity_, false, this); this.hasActivityListener_ = false; } }; /** @override */ goog.ui.IdleTimer.prototype.disposeInternal = function() { this.removeActivityListener_(); if (this.onActivityTimerId_ != null) { goog.global.clearTimeout(this.onActivityTimerId_); this.onActivityTimerId_ = null; } this.maybeDisposeDefaultActivityMonitor_(); goog.ui.IdleTimer.superClass_.disposeInternal.call(this); }; /** * @return {number} the amount of time at which we consider the user has gone * idle in ms. */ goog.ui.IdleTimer.prototype.getIdleThreshold = function() { return this.idleThreshold_; }; /** * @return {goog.ui.ActivityMonitor} the activity monitor keeping track of user * interaction. */ goog.ui.IdleTimer.prototype.getActivityMonitor = function() { return this.activityMonitor_; }; /** * Returns true if there has been no user action for at least the specified * interval, and false otherwise * @return {boolean} true if the user is idle, false otherwise. */ goog.ui.IdleTimer.prototype.isIdle = function() { return this.isIdle_; };