| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * Overrides timeout and interval callbacks to mock timing behavior. |
| * @constructor |
| */ |
| function MockTimer() { |
| /** |
| * Default versions of the timing functions. |
| * @type {Object<string, !Function>} |
| * @private |
| */ |
| this.originals_ = []; |
| |
| /** |
| * Key to assign on the next creation of a scheduled timer. Each call to |
| * setTimeout or setInterval returns a unique key that can be used for |
| * clearing the timer. |
| * @type {number} |
| * @private |
| */ |
| this.nextTimerKey_ = 1; |
| |
| /** |
| * Details for active timers. |
| * @type {Array<{callback: Function, |
| * delay: number, |
| * key: number, |
| * repeats: boolean}>} |
| * @private |
| */ |
| this.timers_ = []; |
| |
| /** |
| * List of scheduled tasks. |
| * @type {Array<{when: number, key: number}>} |
| * @private |
| */ |
| this.schedule_ = []; |
| |
| /** |
| * Virtual elapsed time in milliseconds. |
| * @type {number} |
| * @private |
| */ |
| this.now_ = 0; |
| |
| /** |
| * Used to control when scheduled callbacks fire. Calling the 'tick' method |
| * inflates this parameter and triggers callbacks. |
| * @type {number} |
| * @private |
| */ |
| this.until_ = 0; |
| } |
| |
| MockTimer.prototype = { |
| /** |
| * Replaces built-in functions for scheduled callbacks. |
| */ |
| install: function() { |
| this.replace_('setTimeout', this.setTimeout_.bind(this)); |
| this.replace_('clearTimeout', this.clearTimeout_.bind(this)); |
| this.replace_('setInterval', this.setInterval_.bind(this)); |
| this.replace_('clearInterval', this.clearInterval_.bind(this)); |
| }, |
| |
| /** |
| * Restores default behavior for scheduling callbacks. |
| */ |
| uninstall: function() { |
| if (this.originals_) { |
| for (var key in this.originals_) { |
| window[key] = this.originals_[key]; |
| } |
| } |
| }, |
| |
| /** |
| * Overrides a global function. |
| * @param {string} functionName The name of the function. |
| * @param {!Function} replacementFunction The function override. |
| * @private |
| */ |
| replace_: function(functionName, replacementFunction) { |
| this.originals_[functionName] = window[functionName]; |
| window[functionName] = replacementFunction; |
| }, |
| |
| /** |
| * Creates a virtual timer. |
| * @param {!Function} callback The callback function. |
| * @param {number} delayInMs The virtual delay in milliseconds. |
| * @param {boolean} repeats Indicates if the timer repeats. |
| * @return {number} Idetifier for the timer. |
| * @private |
| */ |
| createTimer_: function(callback, delayInMs, repeats) { |
| var key = this.nextTimerKey_++; |
| var task = { |
| callback: callback, |
| delay: delayInMs, |
| key: key, |
| repeats: repeats |
| }; |
| this.timers_[key] = task; |
| this.scheduleTask_(task); |
| return key; |
| }, |
| |
| /** |
| * Schedules a callback for execution after a virtual time delay. The tasks |
| * are sorted in descending order of time delay such that the next callback |
| * to fire is at the end of the list. |
| * @param {{callback: Function, |
| * delay: number, |
| * key: number, |
| * repeats: boolean}} details The timer details. |
| * @private |
| */ |
| scheduleTask_: function(details) { |
| var key = details.key; |
| var when = this.now_ + details.delay; |
| var index = this.schedule_.length; |
| while (index > 0 && this.schedule_[index - 1].when < when) { |
| index--; |
| } |
| this.schedule_.splice(index, 0, {when: when, key: key}); |
| }, |
| |
| /** |
| * Override of window.setInterval. |
| * @param {!Function} callback The callback function. |
| * @param {number} intervalInMs The repeat interval. |
| * @private |
| */ |
| setInterval_: function(callback, intervalInMs) { |
| return this.createTimer_(callback, intervalInMs, true); |
| }, |
| |
| /** |
| * Override of window.clearInterval. |
| * @param {number} key The ID of the interval timer returned from |
| * setInterval. |
| * @private |
| */ |
| clearInterval_: function(key) { |
| this.timers_[key] = undefined; |
| }, |
| |
| /** |
| * Override of window.setTimeout. |
| * @param {!Function} callback The callback function. |
| * @param {number} delayInMs The scheduled delay. |
| * @private |
| */ |
| setTimeout_: function(callback, delayInMs) { |
| return this.createTimer_(callback, delayInMs, false); |
| }, |
| |
| /** |
| * Override of window.clearTimeout. |
| * @param {number} key The ID of the schedule timeout callback returned |
| * from setTimeout. |
| * @private |
| */ |
| clearTimeout_: function(key) { |
| this.timers_[key] = undefined; |
| }, |
| |
| /** |
| * Simulates passage of time, triggering any scheduled callbacks whose timer |
| * has elapsed. |
| * @param {number} elapsedMs The simulated elapsed time in milliseconds. |
| */ |
| tick: function(elapsedMs) { |
| this.until_ += elapsedMs; |
| this.fireElapsedCallbacks_(); |
| }, |
| |
| /** |
| * Triggers any callbacks that should have fired based in the simulated |
| * timing. |
| * @private |
| */ |
| fireElapsedCallbacks_: function() { |
| while (this.schedule_.length > 0) { |
| var when = this.schedule_[this.schedule_.length - 1].when; |
| if (when > this.until_) |
| break; |
| |
| var task = this.schedule_.pop(); |
| var details = this.timers_[task.key]; |
| if (!details) |
| continue; // Cancelled task. |
| |
| this.now_ = when; |
| details.callback.apply(window); |
| if (details.repeats) |
| this.scheduleTask_(details); |
| else |
| this.clearTimeout_(details.key); |
| } |
| this.now_ = this.until_; |
| }, |
| |
| }; |