| // Copyright 2006 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 A timer class to which other classes and objects can |
| * listen on. This is only an abstraction above setInterval. |
| * |
| * @see ../demos/timers.html |
| */ |
| |
| goog.provide('goog.Timer'); |
| |
| goog.require('goog.events.EventTarget'); |
| |
| |
| |
| /** |
| * Class for handling timing events. |
| * |
| * @param {number=} opt_interval Number of ms between ticks (Default: 1ms). |
| * @param {Object=} opt_timerObject An object that has setTimeout, setInterval, |
| * clearTimeout and clearInterval (eg Window). |
| * @constructor |
| * @extends {goog.events.EventTarget} |
| */ |
| goog.Timer = function(opt_interval, opt_timerObject) { |
| goog.events.EventTarget.call(this); |
| |
| /** |
| * Number of ms between ticks |
| * @type {number} |
| * @private |
| */ |
| this.interval_ = opt_interval || 1; |
| |
| /** |
| * An object that implements setTimeout, setInterval, clearTimeout and |
| * clearInterval. We default to the window object. Changing this on |
| * goog.Timer.prototype changes the object for all timer instances which can |
| * be useful if your environment has some other implementation of timers than |
| * the window object. |
| * @type {Object} |
| * @private |
| */ |
| this.timerObject_ = opt_timerObject || goog.Timer.defaultTimerObject; |
| |
| /** |
| * Cached tick_ bound to the object for later use in the timer. |
| * @type {Function} |
| * @private |
| */ |
| this.boundTick_ = goog.bind(this.tick_, this); |
| |
| /** |
| * Firefox browser often fires the timer event sooner |
| * (sometimes MUCH sooner) than the requested timeout. So we |
| * compare the time to when the event was last fired, and |
| * reschedule if appropriate. See also goog.Timer.intervalScale |
| * @type {number} |
| * @private |
| */ |
| this.last_ = goog.now(); |
| }; |
| goog.inherits(goog.Timer, goog.events.EventTarget); |
| |
| |
| /** |
| * Maximum timeout value. |
| * |
| * Timeout values too big to fit into a signed 32-bit integer may cause |
| * overflow in FF, Safari, and Chrome, resulting in the timeout being |
| * scheduled immediately. It makes more sense simply not to schedule these |
| * timeouts, since 24.8 days is beyond a reasonable expectation for the |
| * browser to stay open. |
| * |
| * @type {number} |
| * @private |
| */ |
| goog.Timer.MAX_TIMEOUT_ = 2147483647; |
| |
| |
| /** |
| * Whether this timer is enabled |
| * @type {boolean} |
| */ |
| goog.Timer.prototype.enabled = false; |
| |
| |
| /** |
| * An object that implements setTimout, setInterval, clearTimeout and |
| * clearInterval. We default to the global object. Changing |
| * goog.Timer.defaultTimerObject changes the object for all timer instances |
| * which can be useful if your environment has some other implementation of |
| * timers you'd like to use. |
| * @type {Object} |
| */ |
| goog.Timer.defaultTimerObject = goog.global; |
| |
| |
| /** |
| * A variable that controls the timer error correction. If the |
| * timer is called before the requested interval times |
| * intervalScale, which often happens on mozilla, the timer is |
| * rescheduled. See also this.last_ |
| * @type {number} |
| */ |
| goog.Timer.intervalScale = 0.8; |
| |
| |
| /** |
| * Variable for storing the result of setInterval |
| * @type {?number} |
| * @private |
| */ |
| goog.Timer.prototype.timer_ = null; |
| |
| |
| /** |
| * Gets the interval of the timer. |
| * @return {number} interval Number of ms between ticks. |
| */ |
| goog.Timer.prototype.getInterval = function() { |
| return this.interval_; |
| }; |
| |
| |
| /** |
| * Sets the interval of the timer. |
| * @param {number} interval Number of ms between ticks. |
| */ |
| goog.Timer.prototype.setInterval = function(interval) { |
| this.interval_ = interval; |
| if (this.timer_ && this.enabled) { |
| // Stop and then start the timer to reset the interval. |
| this.stop(); |
| this.start(); |
| } else if (this.timer_) { |
| this.stop(); |
| } |
| }; |
| |
| |
| /** |
| * Callback for the setTimeout used by the timer |
| * @private |
| */ |
| goog.Timer.prototype.tick_ = function() { |
| if (this.enabled) { |
| var elapsed = goog.now() - this.last_; |
| if (elapsed > 0 && |
| elapsed < this.interval_ * goog.Timer.intervalScale) { |
| this.timer_ = this.timerObject_.setTimeout(this.boundTick_, |
| this.interval_ - elapsed); |
| return; |
| } |
| |
| // Prevents setInterval from registering a duplicate timeout when called |
| // in the timer event handler. |
| if (this.timer_) { |
| this.timerObject_.clearTimeout(this.timer_); |
| this.timer_ = null; |
| } |
| |
| this.dispatchTick(); |
| // The timer could be stopped in the timer event handler. |
| if (this.enabled) { |
| this.timer_ = this.timerObject_.setTimeout(this.boundTick_, |
| this.interval_); |
| this.last_ = goog.now(); |
| } |
| } |
| }; |
| |
| |
| /** |
| * Dispatches the TICK event. This is its own method so subclasses can override. |
| */ |
| goog.Timer.prototype.dispatchTick = function() { |
| this.dispatchEvent(goog.Timer.TICK); |
| }; |
| |
| |
| /** |
| * Starts the timer. |
| */ |
| goog.Timer.prototype.start = function() { |
| this.enabled = true; |
| |
| // If there is no interval already registered, start it now |
| if (!this.timer_) { |
| // IMPORTANT! |
| // window.setInterval in FireFox has a bug - it fires based on |
| // absolute time, rather than on relative time. What this means |
| // is that if a computer is sleeping/hibernating for 24 hours |
| // and the timer interval was configured to fire every 1000ms, |
| // then after the PC wakes up the timer will fire, in rapid |
| // succession, 3600*24 times. |
| // This bug is described here and is already fixed, but it will |
| // take time to propagate, so for now I am switching this over |
| // to setTimeout logic. |
| // https://bugzilla.mozilla.org/show_bug.cgi?id=376643 |
| // |
| this.timer_ = this.timerObject_.setTimeout(this.boundTick_, |
| this.interval_); |
| this.last_ = goog.now(); |
| } |
| }; |
| |
| |
| /** |
| * Stops the timer. |
| */ |
| goog.Timer.prototype.stop = function() { |
| this.enabled = false; |
| if (this.timer_) { |
| this.timerObject_.clearTimeout(this.timer_); |
| this.timer_ = null; |
| } |
| }; |
| |
| |
| /** @override */ |
| goog.Timer.prototype.disposeInternal = function() { |
| goog.Timer.superClass_.disposeInternal.call(this); |
| this.stop(); |
| delete this.timerObject_; |
| }; |
| |
| |
| /** |
| * Constant for the timer's event type |
| * @type {string} |
| */ |
| goog.Timer.TICK = 'tick'; |
| |
| |
| /** |
| * Calls the given function once, after the optional pause. |
| * |
| * The function is always called asynchronously, even if the delay is 0. This |
| * is a common trick to schedule a function to run after a batch of browser |
| * event processing. |
| * |
| * @param {function(this:SCOPE)|{handleEvent:function()}|null} listener Function |
| * or object that has a handleEvent method. |
| * @param {number=} opt_delay Milliseconds to wait; default is 0. |
| * @param {SCOPE=} opt_handler Object in whose scope to call the listener. |
| * @return {number} A handle to the timer ID. |
| * @template SCOPE |
| */ |
| goog.Timer.callOnce = function(listener, opt_delay, opt_handler) { |
| if (goog.isFunction(listener)) { |
| if (opt_handler) { |
| listener = goog.bind(listener, opt_handler); |
| } |
| } else if (listener && typeof listener.handleEvent == 'function') { |
| // using typeof to prevent strict js warning |
| listener = goog.bind(listener.handleEvent, listener); |
| } else { |
| throw Error('Invalid listener argument'); |
| } |
| |
| if (opt_delay > goog.Timer.MAX_TIMEOUT_) { |
| // Timeouts greater than MAX_INT return immediately due to integer |
| // overflow in many browsers. Since MAX_INT is 24.8 days, just don't |
| // schedule anything at all. |
| return -1; |
| } else { |
| return goog.Timer.defaultTimerObject.setTimeout( |
| listener, opt_delay || 0); |
| } |
| }; |
| |
| |
| /** |
| * Clears a timeout initiated by callOnce |
| * @param {?number} timerId a timer ID. |
| */ |
| goog.Timer.clear = function(timerId) { |
| goog.Timer.defaultTimerObject.clearTimeout(timerId); |
| }; |