/**
* @license
* Copyright The Closure Library Authors.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @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');
goog.requireType('goog.events.Event');
/**
* 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) {
'use strict';
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() {
'use strict';
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() {
'use strict';
goog.ui.IdleTimer.defaultActivityMonitorReferences_.remove(this);
if (goog.ui.IdleTimer.defaultActivityMonitor_ != null &&
goog.ui.IdleTimer.defaultActivityMonitorReferences_.size === 0) {
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() {
'use strict';
// 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 -
(Date.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() {
'use strict';
// 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() {
'use strict';
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) {
'use strict';
this.becomeActive_();
};
/**
* Transitions from the idle state to the active state
* @private
*/
goog.ui.IdleTimer.prototype.becomeActive_ = function() {
'use strict';
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() {
'use strict';
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() {
'use strict';
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() {
'use strict';
return this.idleThreshold_;
};
/**
* @return {goog.ui.ActivityMonitor} the activity monitor keeping track of user
* interaction.
*/
goog.ui.IdleTimer.prototype.getActivityMonitor = function() {
'use strict';
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() {
'use strict';
return this.isIdle_;
};