activitymonitor.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. // Copyright 2006 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Activity Monitor.
  16. *
  17. * Fires throttled events when a user interacts with the specified document.
  18. * This class also exposes the amount of time since the last user event.
  19. *
  20. * If you would prefer to get BECOME_ACTIVE and BECOME_IDLE events when the
  21. * user changes states, then you should use the IdleTimer class instead.
  22. *
  23. */
  24. goog.provide('goog.ui.ActivityMonitor');
  25. goog.require('goog.array');
  26. goog.require('goog.asserts');
  27. goog.require('goog.dom');
  28. goog.require('goog.events.EventHandler');
  29. goog.require('goog.events.EventTarget');
  30. goog.require('goog.events.EventType');
  31. /**
  32. * Once initialized with a document, the activity monitor can be queried for
  33. * the current idle time.
  34. *
  35. * @param {goog.dom.DomHelper|Array<goog.dom.DomHelper>=} opt_domHelper
  36. * DomHelper which contains the document(s) to listen to. If null, the
  37. * default document is usedinstead.
  38. * @param {boolean=} opt_useBubble Whether to use the bubble phase to listen for
  39. * events. By default listens on the capture phase so that it won't miss
  40. * events that get stopPropagation/cancelBubble'd. However, this can cause
  41. * problems in IE8 if the page loads multiple scripts that include the
  42. * closure event handling code.
  43. *
  44. * @constructor
  45. * @extends {goog.events.EventTarget}
  46. */
  47. goog.ui.ActivityMonitor = function(opt_domHelper, opt_useBubble) {
  48. goog.events.EventTarget.call(this);
  49. /**
  50. * Array of documents that are being listened to.
  51. * @type {Array<Document>}
  52. * @private
  53. */
  54. this.documents_ = [];
  55. /**
  56. * Whether to use the bubble phase to listen for events.
  57. * @type {boolean}
  58. * @private
  59. */
  60. this.useBubble_ = !!opt_useBubble;
  61. /**
  62. * The event handler.
  63. * @type {goog.events.EventHandler<!goog.ui.ActivityMonitor>}
  64. * @private
  65. */
  66. this.eventHandler_ = new goog.events.EventHandler(this);
  67. /**
  68. * Whether the current window is an iframe.
  69. * TODO(user): Move to goog.dom.
  70. * @type {boolean}
  71. * @private
  72. */
  73. this.isIframe_ = window.parent != window;
  74. if (!opt_domHelper) {
  75. this.addDocument(goog.dom.getDomHelper().getDocument());
  76. } else if (goog.isArray(opt_domHelper)) {
  77. for (var i = 0; i < opt_domHelper.length; i++) {
  78. this.addDocument(opt_domHelper[i].getDocument());
  79. }
  80. } else {
  81. this.addDocument(opt_domHelper.getDocument());
  82. }
  83. /**
  84. * The time (in milliseconds) of the last user event.
  85. * @type {number}
  86. * @private
  87. */
  88. this.lastEventTime_ = goog.now();
  89. };
  90. goog.inherits(goog.ui.ActivityMonitor, goog.events.EventTarget);
  91. goog.tagUnsealableClass(goog.ui.ActivityMonitor);
  92. /**
  93. * The last event type that was detected.
  94. * @type {string}
  95. * @private
  96. */
  97. goog.ui.ActivityMonitor.prototype.lastEventType_ = '';
  98. /**
  99. * The mouse x-position after the last user event.
  100. * @type {number}
  101. * @private
  102. */
  103. goog.ui.ActivityMonitor.prototype.lastMouseX_;
  104. /**
  105. * The mouse y-position after the last user event.
  106. * @type {number}
  107. * @private
  108. */
  109. goog.ui.ActivityMonitor.prototype.lastMouseY_;
  110. /**
  111. * The earliest time that another throttled ACTIVITY event will be dispatched
  112. * @type {number}
  113. * @private
  114. */
  115. goog.ui.ActivityMonitor.prototype.minEventTime_ = 0;
  116. /**
  117. * Minimum amount of time in ms between throttled ACTIVITY events
  118. * @type {number}
  119. */
  120. goog.ui.ActivityMonitor.MIN_EVENT_SPACING = 3 * 1000;
  121. /**
  122. * If a user executes one of these events, s/he is considered not idle.
  123. * @type {Array<goog.events.EventType>}
  124. * @private
  125. */
  126. goog.ui.ActivityMonitor.userEventTypesBody_ = [
  127. goog.events.EventType.CLICK, goog.events.EventType.DBLCLICK,
  128. goog.events.EventType.MOUSEDOWN, goog.events.EventType.MOUSEMOVE,
  129. goog.events.EventType.MOUSEUP
  130. ];
  131. /**
  132. * If a user executes one of these events, s/he is considered not idle.
  133. * Note: monitoring touch events within iframe cause problems in iOS.
  134. * @type {Array<goog.events.EventType>}
  135. * @private
  136. */
  137. goog.ui.ActivityMonitor.userTouchEventTypesBody_ = [
  138. goog.events.EventType.TOUCHEND, goog.events.EventType.TOUCHMOVE,
  139. goog.events.EventType.TOUCHSTART
  140. ];
  141. /**
  142. * If a user executes one of these events, s/he is considered not idle.
  143. * @type {Array<goog.events.EventType>}
  144. * @private
  145. */
  146. goog.ui.ActivityMonitor.userEventTypesDocuments_ =
  147. [goog.events.EventType.KEYDOWN, goog.events.EventType.KEYUP];
  148. /**
  149. * Event constants for the activity monitor.
  150. * @enum {string}
  151. */
  152. goog.ui.ActivityMonitor.Event = {
  153. /** Event fired when the user does something interactive */
  154. ACTIVITY: 'activity'
  155. };
  156. /** @override */
  157. goog.ui.ActivityMonitor.prototype.disposeInternal = function() {
  158. goog.ui.ActivityMonitor.superClass_.disposeInternal.call(this);
  159. this.eventHandler_.dispose();
  160. this.eventHandler_ = null;
  161. delete this.documents_;
  162. };
  163. /**
  164. * Adds a document to those being monitored by this class.
  165. *
  166. * @param {Document} doc Document to monitor.
  167. */
  168. goog.ui.ActivityMonitor.prototype.addDocument = function(doc) {
  169. if (goog.array.contains(this.documents_, doc)) {
  170. return;
  171. }
  172. this.documents_.push(doc);
  173. var useCapture = !this.useBubble_;
  174. var eventsToListenTo = goog.array.concat(
  175. goog.ui.ActivityMonitor.userEventTypesDocuments_,
  176. goog.ui.ActivityMonitor.userEventTypesBody_);
  177. if (!this.isIframe_) {
  178. // Monitoring touch events in iframe causes problems interacting with text
  179. // fields in iOS (input text, textarea, contenteditable, select/copy/paste),
  180. // so just ignore these events. This shouldn't matter much given that a
  181. // touchstart event followed by touchend event produces a click event,
  182. // which is being monitored correctly.
  183. goog.array.extend(
  184. eventsToListenTo, goog.ui.ActivityMonitor.userTouchEventTypesBody_);
  185. }
  186. this.eventHandler_.listen(
  187. doc, eventsToListenTo, this.handleEvent_, useCapture);
  188. };
  189. /**
  190. * Removes a document from those being monitored by this class.
  191. *
  192. * @param {Document} doc Document to monitor.
  193. */
  194. goog.ui.ActivityMonitor.prototype.removeDocument = function(doc) {
  195. if (this.isDisposed()) {
  196. return;
  197. }
  198. goog.array.remove(this.documents_, doc);
  199. var useCapture = !this.useBubble_;
  200. var eventsToUnlistenTo = goog.array.concat(
  201. goog.ui.ActivityMonitor.userEventTypesDocuments_,
  202. goog.ui.ActivityMonitor.userEventTypesBody_);
  203. if (!this.isIframe_) {
  204. // See note above about monitoring touch events in iframe.
  205. goog.array.extend(
  206. eventsToUnlistenTo, goog.ui.ActivityMonitor.userTouchEventTypesBody_);
  207. }
  208. this.eventHandler_.unlisten(
  209. doc, eventsToUnlistenTo, this.handleEvent_, useCapture);
  210. };
  211. /**
  212. * Updates the last event time when a user action occurs.
  213. * @param {goog.events.BrowserEvent} e Event object.
  214. * @private
  215. */
  216. goog.ui.ActivityMonitor.prototype.handleEvent_ = function(e) {
  217. var update = false;
  218. switch (e.type) {
  219. case goog.events.EventType.MOUSEMOVE:
  220. // In FF 1.5, we get spurious mouseover and mouseout events when the UI
  221. // redraws. We only want to update the idle time if the mouse has moved.
  222. if (typeof this.lastMouseX_ == 'number' &&
  223. this.lastMouseX_ != e.clientX ||
  224. typeof this.lastMouseY_ == 'number' &&
  225. this.lastMouseY_ != e.clientY) {
  226. update = true;
  227. }
  228. this.lastMouseX_ = e.clientX;
  229. this.lastMouseY_ = e.clientY;
  230. break;
  231. default:
  232. update = true;
  233. }
  234. if (update) {
  235. var type = goog.asserts.assertString(e.type);
  236. this.updateIdleTime(goog.now(), type);
  237. }
  238. };
  239. /**
  240. * Updates the last event time to be the present time, useful for non-DOM
  241. * events that should update idle time.
  242. */
  243. goog.ui.ActivityMonitor.prototype.resetTimer = function() {
  244. this.updateIdleTime(goog.now(), 'manual');
  245. };
  246. /**
  247. * Updates the idle time and fires an event if time has elapsed since
  248. * the last update.
  249. * @param {number} eventTime Time (in MS) of the event that cleared the idle
  250. * timer.
  251. * @param {string} eventType Type of the event, used only for debugging.
  252. * @protected
  253. */
  254. goog.ui.ActivityMonitor.prototype.updateIdleTime = function(
  255. eventTime, eventType) {
  256. // update internal state noting whether the user was idle
  257. this.lastEventTime_ = eventTime;
  258. this.lastEventType_ = eventType;
  259. // dispatch event
  260. if (eventTime > this.minEventTime_) {
  261. this.dispatchEvent(goog.ui.ActivityMonitor.Event.ACTIVITY);
  262. this.minEventTime_ = eventTime + goog.ui.ActivityMonitor.MIN_EVENT_SPACING;
  263. }
  264. };
  265. /**
  266. * Returns the amount of time the user has been idle.
  267. * @param {number=} opt_now The current time can optionally be passed in for the
  268. * computation to avoid an extra Date allocation.
  269. * @return {number} The amount of time in ms that the user has been idle.
  270. */
  271. goog.ui.ActivityMonitor.prototype.getIdleTime = function(opt_now) {
  272. var now = opt_now || goog.now();
  273. return now - this.lastEventTime_;
  274. };
  275. /**
  276. * Returns the type of the last user event.
  277. * @return {string} event type.
  278. */
  279. goog.ui.ActivityMonitor.prototype.getLastEventType = function() {
  280. return this.lastEventType_;
  281. };
  282. /**
  283. * Returns the time of the last event
  284. * @return {number} last event time.
  285. */
  286. goog.ui.ActivityMonitor.prototype.getLastEventTime = function() {
  287. return this.lastEventTime_;
  288. };