Path: blob/trunk/third_party/closure/goog/testing/mockclock.js
4500 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview Mock Clock implementation for working with setTimeout,8* setInterval, clearTimeout and clearInterval within unit tests.9*10* Derived from jsUnitMockTimeout.js, contributed to JsUnit by11* Pivotal Computer Systems, www.pivotalsf.com12*/1314goog.setTestOnly('goog.testing.MockClock');15goog.provide('goog.testing.MockClock');1617goog.require('goog.Disposable');18/** @suppress {extraRequire} */19goog.require('goog.Promise');20goog.require('goog.Thenable');21goog.require('goog.asserts');22goog.require('goog.async.nextTick');23goog.require('goog.async.run');24goog.require('goog.testing.PropertyReplacer');25goog.require('goog.testing.events');26goog.require('goog.testing.events.Event');27282930/**31* Class for unit testing code that uses setTimeout and clearTimeout.32*33* NOTE: If you are using MockClock to test code that makes use of34* goog.fx.Animation, then you must either:35*36* 1. Install and dispose of the MockClock in setUpPage() and tearDownPage()37* respectively (rather than setUp()/tearDown()).38*39* or40*41* 2. Ensure that every test clears the animation queue by calling42* mockClock.tick(x) at the end of each test function (where `x` is large43* enough to complete all animations).44*45* Otherwise, if any animation is left pending at the time that46* MockClock.dispose() is called, that will permanently prevent any future47* animations from playing on the page.48*49* @param {boolean=} opt_autoInstall Install the MockClock at construction time.50* @constructor51* @extends {goog.Disposable}52* @final53*/54goog.testing.MockClock = function(opt_autoInstall) {55'use strict';56goog.Disposable.call(this);57/**58* Reverse-order queue of timers to fire.59*60* The last item of the queue is popped off. Insertion happens from the61* right. For example, the expiration times for each element of the queue62* might be in the order 300, 200, 200.63*64* @type {?Array<!goog.testing.MockClock.QueueObjType_>}65* @private66*/67this.queue_ = [];6869/**70* Set of timeouts that should be treated as cancelled.71*72* Rather than removing cancelled timers directly from the queue, this set73* simply marks them as deleted so that they can be ignored when their74* turn comes up. The keys are the timeout keys that are cancelled, each75* mapping to true.76*77* @private {?Object<number, boolean>}78*/79this.deletedKeys_ = {};8081/**82* Whether we should skip mocking Date.now().83* @private {boolean}84*/85this.unmockDateNow_ = false;8687if (opt_autoInstall) {88this.install();89}90};91goog.inherits(goog.testing.MockClock, goog.Disposable);929394/**95* @typedef {{96* timeoutKey: number, millis: number,97* runAtMillis: number, funcToCall: !Function, recurring: boolean}}98* @private99*/100goog.testing.MockClock.QueueObjType_;101102/**103* Default wait timeout for mocking requestAnimationFrame (in milliseconds).104*105* @type {number}106* @const107*/108goog.testing.MockClock.REQUEST_ANIMATION_FRAME_TIMEOUT = 20;109110111/**112* ID to use for next timeout. Timeout IDs must never be reused, even across113* MockClock instances.114* @public {number}115*/116goog.testing.MockClock.nextId = Math.round(Math.random() * 10000);117118119/**120* Count of the number of setTimeout/setInterval/etc. calls received by this121* instance.122* @type {number}123* @private124*/125goog.testing.MockClock.prototype.timeoutsMade_ = 0;126127128/**129* Count of the number of timeout/interval/etc. callbacks triggered by this130* instance.131* @type {number}132* @private133*/134goog.testing.MockClock.prototype.callbacksTriggered_ = 0;135136137/**138* PropertyReplacer instance which overwrites and resets setTimeout,139* setInterval, etc. or null if the MockClock is not installed.140* @type {?goog.testing.PropertyReplacer}141* @private142*/143goog.testing.MockClock.prototype.replacer_ = null;144145146/**147* The current simulated time in milliseconds.148* @type {number}149* @private150*/151goog.testing.MockClock.prototype.nowMillis_ = 0;152153154/**155* Additional delay between the time a timeout was set to fire, and the time156* it actually fires. Useful for testing workarounds for this Firefox 2 bug:157* https://bugzilla.mozilla.org/show_bug.cgi?id=291386158* May be negative.159* @type {number}160* @private161*/162goog.testing.MockClock.prototype.timeoutDelay_ = 0;163164165/**166* Whether the MockClock is allowed to use synchronous ticks.167*168* When this is true, MockClock will patch goog.async.run upon installation so169* that GoogPromises can be resolved synchronously.170* @type {boolean}171* @private172*/173goog.testing.MockClock.prototype.isSynchronous_ = true;174175176/**177* Creates an async-only MockClock that can only be ticked asynchronously.178*179* Async-only MockClocks rely on native Promise resolution instead of180* patching async run behavior to force GoogPromise to resolve synchronously.181* As a result, async MockClocks must be ticked with tickAsync() instead of182* tick().183*184* Async-only MockClocks will always use the default async scheduler and will185* never reset the async queue when uninstalled.186*187* @return {!goog.testing.MockClock}188*/189goog.testing.MockClock.createAsyncMockClock = function() {190const clock = new goog.testing.MockClock();191clock.isSynchronous_ = false;192return clock;193};194195/**196* The real set timeout for reference.197* @const @private {!Function}198*/199goog.testing.MockClock.REAL_SETTIMEOUT_ = goog.global.setTimeout;200201202/** @private {function():number} */203goog.testing.MockClock.prototype.oldGoogNow_;204205/**206* Installs the MockClock by overriding the global object's implementation of207* setTimeout, setInterval, clearTimeout and clearInterval.208*/209goog.testing.MockClock.prototype.install = function() {210'use strict';211if (!this.replacer_) {212if (goog.testing.MockClock.REAL_SETTIMEOUT_ !== goog.global.setTimeout) {213if (typeof console !== 'undefined' && console.warn) {214console.warn(215'Non default setTimeout detected. ' +216'Use of multiple MockClock instances or other clock mocking ' +217'should be avoided due to unspecified behavior and ' +218'the resulting fragility.');219}220}221222var r = this.replacer_ = new goog.testing.PropertyReplacer();223r.set(goog.global, 'setTimeout', goog.bind(this.setTimeout_, this));224r.set(goog.global, 'setInterval', goog.bind(this.setInterval_, this));225r.set(goog.global, 'clearTimeout', goog.bind(this.clearTimeout_, this));226r.set(goog.global, 'clearInterval', goog.bind(this.clearInterval_, this));227if (!this.unmockDateNow_) {228r.set(Date, 'now', goog.bind(this.getCurrentTime, this));229}230// goog.async.nextTick depends on various internal browser APIs231// (setImmediate, MessageChannel, and setTimeout), but it's internal232// implementation uses local caching which makes stubbing the browser233// natives not feasible. Stub it directly instead.234r.set(235goog.async.nextTick, 'nextTickImpl',236goog.bind(this.setImmediate_, this));237// setImmediate is a deprecated API that does not exist in most browsers.238// Set it in the browser supports it.239// Preserve existing behavior of synchronous mock clocks to unconditionally240// stub setImmediate.241if (goog.global['setImmediate'] || this.isSynchronous_) {242r.set(goog.global, 'setImmediate', goog.bind(this.setImmediate_, this));243}244245if (this.isSynchronous_) {246// goog.Promise uses goog.async.run. In order to be able to test247// Promise-based code synchronously, we need to make sure that248// goog.async.run uses nextTick instead of native browser Promises. Since249// nextTick calls setImmediate, it will be synchronously executed the250// next time the MockClock is ticked. Note that we test for the presence251// of goog.async.run.forceNextTick to be resilient to the case where252// tests replace goog.async.run directly.253goog.async.run.forceNextTick &&254goog.async.run.forceNextTick(goog.testing.MockClock.REAL_SETTIMEOUT_);255} else {256// Reset the scheduler in case a synchronous MockClock was previously257// installed. Otherwise goog.Promise resolution and other work scheduled258// with goog.async.run would be executed synchronously when ticking the259// clock.260goog.async.run.resetSchedulerForTest &&261goog.async.run.resetSchedulerForTest();262}263264// Replace the requestAnimationFrame functions.265this.replaceRequestAnimationFrame_();266267// PropertyReplacer#set can't be called with renameable functions.268this.oldGoogNow_ = goog.now;269goog.now = goog.bind(this.getCurrentTime, this);270}271};272273274/**275* Unmocks the Date.now() function for tests that aren't expecting it to be276* mocked. See b/141619890.277* @deprecated278*/279goog.testing.MockClock.prototype.unmockDateNow = function() {280'use strict';281this.unmockDateNow_ = true;282if (this.replacer_) {283try {284this.replacer_.restore(Date, 'now');285} catch (e) {286// Ignore error thrown if Date.now was not already mocked.287}288}289};290291292/**293* Installs the mocks for requestAnimationFrame and cancelRequestAnimationFrame.294* @private295*/296goog.testing.MockClock.prototype.replaceRequestAnimationFrame_ = function() {297'use strict';298var r = this.replacer_;299var requestFuncs = [300'requestAnimationFrame', 'webkitRequestAnimationFrame',301'mozRequestAnimationFrame', 'oRequestAnimationFrame',302'msRequestAnimationFrame'303];304305var cancelFuncs = [306'cancelAnimationFrame', 'cancelRequestAnimationFrame',307'webkitCancelRequestAnimationFrame', 'mozCancelRequestAnimationFrame',308'oCancelRequestAnimationFrame', 'msCancelRequestAnimationFrame'309];310311for (var i = 0; i < requestFuncs.length; ++i) {312if (goog.global && goog.global[requestFuncs[i]]) {313r.set(314goog.global, requestFuncs[i],315goog.bind(this.requestAnimationFrame_, this));316}317}318319for (var i = 0; i < cancelFuncs.length; ++i) {320if (goog.global && goog.global[cancelFuncs[i]]) {321r.set(322goog.global, cancelFuncs[i],323goog.bind(this.cancelRequestAnimationFrame_, this));324}325}326};327328329/**330* Removes the MockClock's hooks into the global object's functions and revert331* to their original values.332*333* @param {boolean=} resetScheduler By default, a synchronous MockClock334* will not restore default goog.async behavior upon uninstallation and335* clear any pending async work. This can leave goog.Promises in a state336* where callbacks can never be executed. Set this flag to restore original337* scheduling behavior and retain the async queue. This argument is ignored338* for an async-only MockClock.339*/340goog.testing.MockClock.prototype.uninstall = function(resetScheduler) {341'use strict';342if (this.replacer_) {343this.replacer_.reset();344this.replacer_ = null;345goog.now = this.oldGoogNow_;346}347348if (this.isSynchronous_) {349// Since async-only MockClock instances are always reset on installation,350// they don't need to be reset when uninstalled.351if (resetScheduler) {352// Check for presence of resetScheduler in case users have replaced353// goog.async.run.354goog.async.run.resetSchedulerForTest &&355goog.async.run.resetSchedulerForTest();356} else {357// If the overridden scheduler is not reset, then clear the work queue.358// This prevents any pending goog.Promise resolution or other work359// scheduled with goog.async.run from executing after uninstallation.360this.resetAsyncQueue_();361}362}363};364365366/** @override */367goog.testing.MockClock.prototype.disposeInternal = function() {368'use strict';369this.uninstall();370this.queue_ = null;371this.deletedKeys_ = null;372goog.testing.MockClock.superClass_.disposeInternal.call(this);373};374375376/**377* Resets the MockClock, removing all timeouts that are scheduled and resets378* the fake timer count.379* @param {boolean=} retainAsyncQueue By default, a synchronous MockClock380* will clear any pending async work when reset. This can leave381* goog.Promises in a state where callbacks can never be executed. Set this382* flag to restore original scheduling behavior and retain the async queue.383* This argument is ignored for an async-only MockClock.384*/385goog.testing.MockClock.prototype.reset = function(retainAsyncQueue) {386'use strict';387this.queue_ = [];388this.deletedKeys_ = {};389this.nowMillis_ = 0;390this.timeoutsMade_ = 0;391this.callbacksTriggered_ = 0;392this.timeoutDelay_ = 0;393394if (this.isSynchronous_ && !retainAsyncQueue) {395// If the overridden scheduler is not intended to be reset, then clear the396// work queue. This prevents any pending async work queue items from397// executing after uninstallation.398this.resetAsyncQueue_();399}400};401402403/**404* Resets the async queue when a synchronous MockClock resets.405* @private406*/407goog.testing.MockClock.prototype.resetAsyncQueue_ = function() {408'use strict';409// Synchronous MockClock should reset the async queue so that pending tasks410// are not executed the next time the call stack is emptied.411goog.asserts.assert(412this.isSynchronous_,413'Async queue cannot be reset on async-only async MockClock.');414415goog.async.run.resetQueue();416};417418419/**420* Sets the amount of time between when a timeout is scheduled to fire and when421* it actually fires.422* @param {number} delay The delay in milliseconds. May be negative.423*/424goog.testing.MockClock.prototype.setTimeoutDelay = function(delay) {425'use strict';426this.timeoutDelay_ = delay;427};428429430/**431* @return {number} delay The amount of time between when a timeout is432* scheduled to fire and when it actually fires, in milliseconds. May433* be negative.434*/435goog.testing.MockClock.prototype.getTimeoutDelay = function() {436'use strict';437return this.timeoutDelay_;438};439440441/**442* Increments the MockClock's time by a given number of milliseconds, running443* any functions that are now overdue.444* @param {number=} opt_millis Number of milliseconds to increment the counter.445* If not specified, clock ticks 1 millisecond.446* @return {number} Current mock time in milliseconds.447*/448goog.testing.MockClock.prototype.tick = function(opt_millis) {449'use strict';450goog.asserts.assert(451this.isSynchronous_,452'Async MockClock does not support tick. Use tickAsync() instead.');453if (typeof opt_millis != 'number') {454opt_millis = 1;455}456if (opt_millis < 0) {457throw new Error(458'Time cannot go backwards (cannot tick by ' + opt_millis + ')');459}460var endTime = this.nowMillis_ + opt_millis;461this.runFunctionsWithinRange_(endTime);462// If a scheduled callback called tick() reentrantly, don't rewind time.463this.nowMillis_ = Math.max(this.nowMillis_, endTime);464return endTime;465};466467468/**469* Takes a promise and then ticks the mock clock. If the promise successfully470* resolves, returns the value produced by the promise. If the promise is471* rejected, it throws the rejection as an exception. If the promise is not472* resolved at all, throws an exception.473* Also ticks the general clock by the specified amount.474* Only works with goog.Thenable, hence goog.Promise. Does NOT work with native475* browser promises.476*477* @param {!goog.Thenable<T>} promise A promise that should be resolved after478* the mockClock is ticked for the given opt_millis.479* @param {number=} opt_millis Number of milliseconds to increment the counter.480* If not specified, clock ticks 1 millisecond.481* @return {T}482* @template T483*484* @deprecated Treating Promises as synchronous values is incompatible with485* native promises and async functions. More generally, this code relies on486* promises "pumped" by setTimeout which is not done in production code,487* even for goog.Promise and results unnatural timing between resolved488* promises callback and setTimeout/setInterval callbacks in tests.489*/490goog.testing.MockClock.prototype.tickPromise = function(promise, opt_millis) {491'use strict';492goog.asserts.assert(493this.isSynchronous_, 'Async MockClock does not support tickPromise.');494495let value;496let error;497let resolved = false;498promise.then(499function(v) {500'use strict';501value = v;502resolved = true;503},504function(e) {505'use strict';506error = e;507resolved = true;508});509this.tick(opt_millis);510if (!resolved) {511throw new Error(512'Promise was expected to be resolved after mock clock tick.');513}514if (error) {515throw error;516}517return value;518};519520521/**522* @return {number} The number of timeouts or intervals that have been523* scheduled. A setInterval call is only counted once.524*/525goog.testing.MockClock.prototype.getTimeoutsMade = function() {526'use strict';527return this.timeoutsMade_;528};529530531/**532* @return {number} The number of timeout or interval callbacks that have been533* triggered. For setInterval, each callback is counted separately.534*/535goog.testing.MockClock.prototype.getCallbacksTriggered = function() {536'use strict';537return this.callbacksTriggered_;538};539540541/**542* @return {number} The MockClock's current time in milliseconds.543*/544goog.testing.MockClock.prototype.getCurrentTime = function() {545'use strict';546return this.nowMillis_;547};548549550/**551* @param {number} timeoutKey The timeout key.552* @return {boolean} Whether the timer has been set and not cleared,553* independent of the timeout's expiration. In other words, the timeout554* could have passed or could be scheduled for the future. Either way,555* this function returns true or false depending only on whether the556* provided timeoutKey represents a timeout that has been set and not557* cleared.558*/559goog.testing.MockClock.prototype.isTimeoutSet = function(timeoutKey) {560'use strict';561return timeoutKey < goog.testing.MockClock.nextId &&562timeoutKey >= goog.testing.MockClock.nextId - this.timeoutsMade_ &&563!this.deletedKeys_[timeoutKey];564};565566567/**568* Whether the MockClock is configured to run synchronously.569*570* This allows MockClock consumers to decide whether to tick synchronously or571* asynchronously.572* @return {boolean}573*/574goog.testing.MockClock.prototype.isSynchronous = function() {575return this.isSynchronous_;576};577578579/**580* Runs any function that is scheduled before a certain time. Timeouts can581* be made to fire early or late if timeoutDelay_ is non-0.582* @param {number} endTime The latest time in the range, in milliseconds.583* @private584*/585goog.testing.MockClock.prototype.runFunctionsWithinRange_ = function(endTime) {586'use strict';587// Repeatedly pop off the last item since the queue is always sorted.588while (this.hasQueuedEntriesBefore_(endTime)) {589this.runNextQueuedTimeout_();590}591};592593594/**595* Increments the MockClock's time by a given number of milliseconds, running596* any functions that are now overdue.597* @param {number=} millis Number of milliseconds to increment the counter.598* If not specified, clock ticks 1 millisecond.599* @return {!Promise<number>} Current mock time in milliseconds.600*/601goog.testing.MockClock.prototype.tickAsync = async function(millis = 1) {602if (millis < 0) {603throw new Error(`Time cannot go backwards (cannot tick by ${millis})`);604}605const endTime = this.nowMillis_ + millis;606await this.runFunctionsWithinRangeAsync_(endTime);607// If a scheduled callback called tick() reentrantly, don't rewind time.608this.nowMillis_ = Math.max(this.nowMillis_, endTime);609return endTime;610};611612613/**614* Asynchronously increments the MockClock's time by a given number of615* milliseconds, returning the settled promise value.616* @param {number} millis Number of milliseconds to increment the counter.617* @param {!goog.Thenable<T>} promise A promise that should be resolved after618* the mockClock is ticked for the given opt_millis.619* @return {!Promise<T>} Resolved promise value.620* @throws {!goog.asserts.AssertionError} when the promise is not resolved after621* ticking.622* @throws {*} when the promise is rejected.623* @template T624*/625goog.testing.MockClock.prototype.tickAsyncMustSettlePromise =626async function(millis, promise) {627goog.asserts.assert(628!this.isSynchronous_,629'Synchronous MockClock does not support tickAsyncMustSettlePromise.');630631let settled = false;632let value;633let error;634promise.then(635(v) => {636settled = true;637value = v;638},639(e) => {640settled = true;641error = e;642});643await this.tickAsync(millis);644goog.asserts.assert(645settled, 'Promise was expected to be resolved after mock clock tick.');646if (error !== undefined) {647throw error;648}649return value;650};651652653/**654* Instantly adjusts the clock's current time to a new timestamp. Unlike tick(),655* this method skips over the intervening time, so that `setInterval()` calls or656* recurring `setTimeout()`s will only run once.657*658* This mimics the behavior of setting the system clock, rather than waiting for659* time to pass.660*661* CAUTION: This is an advanced feature. Use this method to set the clock to be662* a specific date, which is much faster than calling tick() with a large value.663* This lets you test code against arbitrary dates.664*665* MOE:begin_strip666* See go/mockclock-time-travel for how & why to use this method.667* MOE:end_strip668*669* @param {!Date} newDate The new timestamp to set the clock to.670* @return {!Promise}671*/672goog.testing.MockClock.prototype.doTimeWarpAsync = async function(newDate) {673goog.asserts.assertInstanceof(674newDate, Date,675'doTimeWarpAsync() only accepts dates. Use tickAsync() instead.');676if (+newDate < this.nowMillis_) {677throw new Error(`Time cannot go backwards (cannot time warp from ${678new Date(this.nowMillis_)} to ${newDate})`);679}680// Adjust the clock before calling the functions, so that they schedule future681// callbacks from the new time.682this.nowMillis_ = +newDate;683await this.runFunctionsWithinRangeAsync_(this.nowMillis_);684};685686687/**688* Like runFunctionsWithinRange, but pauses to allow native promise callbacks to689* run correctly.690* @param {number} endTime The latest time in the range, in milliseconds.691* @return {!Promise}692* @private693*/694goog.testing.MockClock.prototype.runFunctionsWithinRangeAsync_ =695async function(endTime) {696'use strict';697// Let native promises set timers before we start ticking.698await goog.testing.MockClock.flushMicroTasks_();699700// Repeatedly pop off the last item since the queue is always sorted.701while (this.hasQueuedEntriesBefore_(endTime)) {702if (this.runNextQueuedTimeout_()) {703await goog.testing.MockClock.flushMicroTasks_();704}705}706};707708709/**710* Pauses asynchronously to run all promise callbacks in the microtask queue.711*712* This is optimized to be correct, but to also not be too slow in IE. It waits713* for up to 50 chained `then()` callbacks at once. Microtasks callbacks are run714* in batches, so a series of `then()` callbacks scheduled at the same time will715* run at once. The loop is only necessary for to run very deep promise chains.716*717* Using `setTimeout()`, `setImmediate()`, or a polyfill would make this better,718* but also makes it 15x slower in IE. Without IE, setImmediate and polyfill is719* best option.720* @private721*/722goog.testing.MockClock.flushMicroTasks_ = async function() {723'use strict';724for (var i = 0; i < 50; i++) {725await Promise.resolve();726}727};728729730/**731* @param {number} endTime The latest time in the range, in milliseconds.732* @return {boolean}733* @private734*/735goog.testing.MockClock.prototype.hasQueuedEntriesBefore_ = function(endTime) {736'use strict';737var adjustedEndTime = endTime - this.timeoutDelay_;738return !!this.queue_ && !!this.queue_.length &&739this.queue_[this.queue_.length - 1].runAtMillis <= adjustedEndTime;740};741742743/**744* Runs the next timeout in the queue, advancing the clock.745* @return {boolean} False if the timeout was cancelled (and nothing happened).746* @private747*/748goog.testing.MockClock.prototype.runNextQueuedTimeout_ = function() {749'use strict';750var timeout = this.queue_.pop();751752if (timeout.timeoutKey in this.deletedKeys_) return false;753754// Only move time forwards.755this.nowMillis_ =756Math.max(this.nowMillis_, timeout.runAtMillis + this.timeoutDelay_);757// Call timeout in global scope and pass the timeout key as the argument.758this.callbacksTriggered_++;759timeout.funcToCall.call(goog.global, timeout.timeoutKey);760// In case the interval was cleared in the funcToCall761if (timeout.recurring) {762this.scheduleFunction_(763timeout.timeoutKey, timeout.funcToCall, timeout.millis, true);764}765return true;766};767768769/**770* Schedules a function to be run at a certain time.771* @param {number} timeoutKey The timeout key.772* @param {!Function} funcToCall The function to call.773* @param {number} millis The number of milliseconds to call it in.774* @param {boolean} recurring Whether to function call should recur.775* @private776*/777goog.testing.MockClock.prototype.scheduleFunction_ = function(778timeoutKey, funcToCall, millis, recurring) {779'use strict';780if (typeof funcToCall !== 'function') {781// Early error for debuggability rather than dying in the next .tick()782throw new TypeError(783'The provided callback must be a function, not a ' + typeof funcToCall);784}785786var /** !goog.testing.MockClock.QueueObjType_ */ timeout = {787runAtMillis: this.nowMillis_ + millis,788funcToCall: funcToCall,789recurring: recurring,790timeoutKey: timeoutKey,791millis: millis792};793794goog.testing.MockClock.insert_(timeout, goog.asserts.assert(this.queue_));795};796797798/**799* Inserts a timer descriptor into a descending-order queue.800*801* Later-inserted duplicates appear at lower indices. For example, the802* asterisk in (5,4,*,3,2,1) would be the insertion point for 3.803*804* @param {!goog.testing.MockClock.QueueObjType_} timeout The timeout to insert,805* with numerical runAtMillis property.806* @param {!Array<!goog.testing.MockClock.QueueObjType_>} queue The queue to807* insert into, with each element having a numerical runAtMillis property.808* @private809*/810goog.testing.MockClock.insert_ = function(timeout, queue) {811'use strict';812// Although insertion of N items is quadratic, requiring goog.structs.Heap813// from a unit test will make tests more prone to breakage. Since unit814// tests are normally small, scalability is not a primary issue.815816// Find an insertion point. Since the queue is in reverse order (so we817// can pop rather than unshift), and later timers with the same time stamp818// should be executed later, we look for the element strictly greater than819// the one we are inserting.820821for (var i = queue.length; i != 0; i--) {822if (queue[i - 1].runAtMillis > timeout.runAtMillis) {823break;824}825queue[i] = queue[i - 1];826}827828queue[i] = timeout;829};830831832/**833* Maximum 32-bit signed integer.834*835* Timeouts over this time return immediately in many browsers, due to integer836* overflow. Such known browsers include Firefox, Chrome, and Safari, but not837* IE.838*839* @type {number}840* @private841*/842goog.testing.MockClock.MAX_INT_ = 2147483647;843844845/**846* Schedules a function to be called after `millis` milliseconds.847* Mock implementation for setTimeout.848* @param {!Function} funcToCall The function to call.849* @param {number=} opt_millis The number of milliseconds to call it after.850* @return {number} The number of timeouts created.851* @private852*/853goog.testing.MockClock.prototype.setTimeout_ = function(854funcToCall, opt_millis) {855'use strict';856var millis = opt_millis || 0;857if (millis > goog.testing.MockClock.MAX_INT_) {858throw new Error(859'Bad timeout value: ' + millis + '. Timeouts over MAX_INT ' +860'(24.8 days) cause timeouts to be fired ' +861'immediately in most browsers, except for IE.');862}863this.timeoutsMade_++;864this.scheduleFunction_(865goog.testing.MockClock.nextId, funcToCall, millis, false);866return goog.testing.MockClock.nextId++;867};868869870/**871* Schedules a function to be called every `millis` milliseconds.872* Mock implementation for setInterval.873* @param {!Function} funcToCall The function to call.874* @param {number=} opt_millis The number of milliseconds between calls.875* @return {number} The number of timeouts created.876* @private877*/878goog.testing.MockClock.prototype.setInterval_ = function(879funcToCall, opt_millis) {880'use strict';881var millis = opt_millis || 0;882this.timeoutsMade_++;883this.scheduleFunction_(884goog.testing.MockClock.nextId, funcToCall, millis, true);885return goog.testing.MockClock.nextId++;886};887888889/**890* Schedules a function to be called when an animation frame is triggered.891* Mock implementation for requestAnimationFrame.892* @param {!Function} funcToCall The function to call.893* @return {number} The number of timeouts created.894* @private895*/896goog.testing.MockClock.prototype.requestAnimationFrame_ = function(funcToCall) {897'use strict';898return this.setTimeout_(goog.bind(function() {899'use strict';900if (funcToCall) {901funcToCall(this.getCurrentTime());902} else if (goog.global.mozRequestAnimationFrame) {903var event = new goog.testing.events.Event('MozBeforePaint', goog.global);904event['timeStamp'] = this.getCurrentTime();905goog.testing.events.fireBrowserEvent(event);906}907}, this), goog.testing.MockClock.REQUEST_ANIMATION_FRAME_TIMEOUT);908};909910911/**912* Schedules a function to be called immediately after the current JS913* execution.914* Mock implementation for setImmediate.915* @param {!Function} funcToCall The function to call.916* @return {number} The number of timeouts created.917* @private918*/919goog.testing.MockClock.prototype.setImmediate_ = function(funcToCall) {920'use strict';921return this.setTimeout_(funcToCall, 0);922};923924925/**926* Clears a timeout.927* Mock implementation for clearTimeout.928* @param {number} timeoutKey The timeout key to clear.929* @private930*/931goog.testing.MockClock.prototype.clearTimeout_ = function(timeoutKey) {932'use strict';933// Some common libraries register static state with timers.934// This is bad. It leads to all sorts of crazy test problems where935// 1) Test A sets up a new mock clock and a static timer.936// 2) Test B sets up a new mock clock, but re-uses the static timer937// from Test A.938// 3) A timeout key from test A gets cleared, breaking a timeout in939// Test B.940//941// For now, we just hackily fail silently if someone tries to clear a timeout942// key before we've allocated it.943// Ideally, we should throw an exception if we see this happening.944if (this.isTimeoutSet(timeoutKey)) {945this.deletedKeys_[timeoutKey] = true;946}947};948949950/**951* Clears an interval.952* Mock implementation for clearInterval.953* @param {number} timeoutKey The interval key to clear.954* @private955*/956goog.testing.MockClock.prototype.clearInterval_ = function(timeoutKey) {957'use strict';958this.clearTimeout_(timeoutKey);959};960961962/**963* Clears a requestAnimationFrame.964* Mock implementation for cancelRequestAnimationFrame.965* @param {number} timeoutKey The requestAnimationFrame key to clear.966* @private967*/968goog.testing.MockClock.prototype.cancelRequestAnimationFrame_ = function(969timeoutKey) {970'use strict';971this.clearTimeout_(timeoutKey);972};973974975