Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/timer/timer.js
4503 views
1
/**
2
* @license
3
* Copyright The Closure Library Authors.
4
* SPDX-License-Identifier: Apache-2.0
5
*/
6
7
/**
8
* @fileoverview A timer class to which other classes and objects can listen on.
9
* This is only an abstraction above `setInterval`.
10
*
11
* @see ../demos/timers.html
12
*/
13
14
goog.provide('goog.Timer');
15
16
goog.require('goog.Promise');
17
goog.require('goog.events.EventTarget');
18
goog.requireType('goog.Thenable');
19
20
21
22
/**
23
* Class for handling timing events.
24
*
25
* @param {number=} opt_interval Number of ms between ticks (default: 1ms).
26
* @param {Object=} opt_timerObject An object that has `setTimeout`,
27
* `setInterval`, `clearTimeout` and `clearInterval`
28
* (e.g., `window`).
29
* @constructor
30
* @extends {goog.events.EventTarget}
31
*/
32
goog.Timer = function(opt_interval, opt_timerObject) {
33
'use strict';
34
goog.events.EventTarget.call(this);
35
36
/**
37
* Number of ms between ticks
38
* @private {number}
39
*/
40
this.interval_ = opt_interval || 1;
41
42
/**
43
* An object that implements `setTimeout`, `setInterval`,
44
* `clearTimeout` and `clearInterval`. We default to the window
45
* object. Changing this on {@link goog.Timer.prototype} changes the object
46
* for all timer instances which can be useful if your environment has some
47
* other implementation of timers than the `window` object.
48
* @private {{setTimeout:!Function, clearTimeout:!Function}}
49
*/
50
this.timerObject_ = /** @type {{setTimeout, clearTimeout}} */ (
51
opt_timerObject || goog.Timer.defaultTimerObject);
52
53
/**
54
* Cached `tick_` bound to the object for later use in the timer.
55
* @private {Function}
56
* @const
57
*/
58
this.boundTick_ = goog.bind(this.tick_, this);
59
60
/**
61
* Firefox browser often fires the timer event sooner (sometimes MUCH sooner)
62
* than the requested timeout. So we compare the time to when the event was
63
* last fired, and reschedule if appropriate. See also
64
* {@link goog.Timer.intervalScale}.
65
* @private {number}
66
*/
67
this.last_ = goog.now();
68
};
69
goog.inherits(goog.Timer, goog.events.EventTarget);
70
71
72
/**
73
* Maximum timeout value.
74
*
75
* Timeout values too big to fit into a signed 32-bit integer may cause overflow
76
* in FF, Safari, and Chrome, resulting in the timeout being scheduled
77
* immediately. It makes more sense simply not to schedule these timeouts, since
78
* 24.8 days is beyond a reasonable expectation for the browser to stay open.
79
*
80
* @private {number}
81
* @const
82
*/
83
goog.Timer.MAX_TIMEOUT_ = 2147483647;
84
85
86
/**
87
* A timer ID that cannot be returned by any known implementation of
88
* `window.setTimeout`. Passing this value to `window.clearTimeout`
89
* should therefore be a no-op.
90
*
91
* @private {number}
92
* @const
93
*/
94
goog.Timer.INVALID_TIMEOUT_ID_ = -1;
95
96
97
/**
98
* Whether this timer is enabled
99
* @type {boolean}
100
*/
101
goog.Timer.prototype.enabled = false;
102
103
104
/**
105
* An object that implements `setTimeout`, `setInterval`,
106
* `clearTimeout` and `clearInterval`. We default to the global
107
* object. Changing `goog.Timer.defaultTimerObject` changes the object for
108
* all timer instances which can be useful if your environment has some other
109
* implementation of timers you'd like to use.
110
* @type {{setTimeout, clearTimeout}}
111
*/
112
goog.Timer.defaultTimerObject = goog.global;
113
114
115
/**
116
* Variable that controls the timer error correction. If the timer is called
117
* before the requested interval times `intervalScale`, which often
118
* happens on Mozilla, the timer is rescheduled.
119
* @see {@link #last_}
120
* @type {number}
121
*/
122
goog.Timer.intervalScale = 0.8;
123
124
125
/**
126
* Variable for storing the result of `setInterval`.
127
* @private {?number}
128
*/
129
goog.Timer.prototype.timer_ = null;
130
131
132
/**
133
* Gets the interval of the timer.
134
* @return {number} interval Number of ms between ticks.
135
*/
136
goog.Timer.prototype.getInterval = function() {
137
'use strict';
138
return this.interval_;
139
};
140
141
142
/**
143
* Sets the interval of the timer.
144
* @param {number} interval Number of ms between ticks.
145
*/
146
goog.Timer.prototype.setInterval = function(interval) {
147
'use strict';
148
this.interval_ = interval;
149
if (this.timer_ && this.enabled) {
150
// Stop and then start the timer to reset the interval.
151
this.stop();
152
this.start();
153
} else if (this.timer_) {
154
this.stop();
155
}
156
};
157
158
159
/**
160
* Callback for the `setTimeout` used by the timer.
161
* @private
162
*/
163
goog.Timer.prototype.tick_ = function() {
164
'use strict';
165
if (this.enabled) {
166
var elapsed = goog.now() - this.last_;
167
if (elapsed > 0 && elapsed < this.interval_ * goog.Timer.intervalScale) {
168
this.timer_ = this.timerObject_.setTimeout(
169
this.boundTick_, this.interval_ - elapsed);
170
return;
171
}
172
173
// Prevents setInterval from registering a duplicate timeout when called
174
// in the timer event handler.
175
if (this.timer_) {
176
this.timerObject_.clearTimeout(this.timer_);
177
this.timer_ = null;
178
}
179
180
this.dispatchTick();
181
// The timer could be stopped in the timer event handler.
182
if (this.enabled) {
183
// Stop and start to ensure there is always only one timeout even if
184
// start is called in the timer event handler.
185
this.stop();
186
this.start();
187
}
188
}
189
};
190
191
192
/**
193
* Dispatches the TICK event. This is its own method so subclasses can override.
194
*/
195
goog.Timer.prototype.dispatchTick = function() {
196
'use strict';
197
this.dispatchEvent(goog.Timer.TICK);
198
};
199
200
201
/**
202
* Starts the timer.
203
*/
204
goog.Timer.prototype.start = function() {
205
'use strict';
206
this.enabled = true;
207
208
// If there is no interval already registered, start it now
209
if (!this.timer_) {
210
// IMPORTANT!
211
// window.setInterval in FireFox has a bug - it fires based on
212
// absolute time, rather than on relative time. What this means
213
// is that if a computer is sleeping/hibernating for 24 hours
214
// and the timer interval was configured to fire every 1000ms,
215
// then after the PC wakes up the timer will fire, in rapid
216
// succession, 3600*24 times.
217
// This bug is described here and is already fixed, but it will
218
// take time to propagate, so for now I am switching this over
219
// to setTimeout logic.
220
// https://bugzilla.mozilla.org/show_bug.cgi?id=376643
221
//
222
this.timer_ = this.timerObject_.setTimeout(this.boundTick_, this.interval_);
223
this.last_ = goog.now();
224
}
225
};
226
227
228
/**
229
* Stops the timer.
230
*/
231
goog.Timer.prototype.stop = function() {
232
'use strict';
233
this.enabled = false;
234
if (this.timer_) {
235
this.timerObject_.clearTimeout(this.timer_);
236
this.timer_ = null;
237
}
238
};
239
240
241
/** @override */
242
goog.Timer.prototype.disposeInternal = function() {
243
'use strict';
244
goog.Timer.superClass_.disposeInternal.call(this);
245
this.stop();
246
delete this.timerObject_;
247
};
248
249
250
/**
251
* Constant for the timer's event type.
252
* @const
253
*/
254
goog.Timer.TICK = 'tick';
255
256
257
/**
258
* Calls the given function once, after the optional pause.
259
* <p>
260
* The function is always called asynchronously, even if the delay is 0. This
261
* is a common trick to schedule a function to run after a batch of browser
262
* event processing.
263
*
264
* @param {function(this:SCOPE)|{handleEvent:function()}|null} listener Function
265
* or object that has a handleEvent method.
266
* @param {number=} opt_delay Milliseconds to wait; default is 0.
267
* @param {SCOPE=} opt_handler Object in whose scope to call the listener.
268
* @return {number} A handle to the timer ID.
269
* @template SCOPE
270
*/
271
goog.Timer.callOnce = function(listener, opt_delay, opt_handler) {
272
'use strict';
273
if (typeof listener === 'function') {
274
if (opt_handler) {
275
listener = goog.bind(listener, opt_handler);
276
}
277
} else if (listener && typeof listener.handleEvent == 'function') {
278
// using typeof to prevent strict js warning
279
listener = goog.bind(listener.handleEvent, listener);
280
} else {
281
throw new Error('Invalid listener argument');
282
}
283
284
if (Number(opt_delay) > goog.Timer.MAX_TIMEOUT_) {
285
// Timeouts greater than MAX_INT return immediately due to integer
286
// overflow in many browsers. Since MAX_INT is 24.8 days, just don't
287
// schedule anything at all.
288
return goog.Timer.INVALID_TIMEOUT_ID_;
289
} else {
290
return goog.Timer.defaultTimerObject.setTimeout(listener, opt_delay || 0);
291
}
292
};
293
294
295
/**
296
* Clears a timeout initiated by {@link #callOnce}.
297
* @param {?number} timerId A timer ID.
298
*/
299
goog.Timer.clear = function(timerId) {
300
'use strict';
301
goog.Timer.defaultTimerObject.clearTimeout(timerId);
302
};
303
304
305
/**
306
* @param {number} delay Milliseconds to wait.
307
* @param {(RESULT|goog.Thenable<RESULT>|Thenable)=} opt_result The value
308
* with which the promise will be resolved.
309
* @return {!goog.Promise<RESULT>} A promise that will be resolved after
310
* the specified delay, unless it is canceled first.
311
* @template RESULT
312
*/
313
goog.Timer.promise = function(delay, opt_result) {
314
'use strict';
315
var timerKey = null;
316
return new goog
317
.Promise(function(resolve, reject) {
318
'use strict';
319
timerKey = goog.Timer.callOnce(function() {
320
'use strict';
321
resolve(opt_result);
322
}, delay);
323
if (timerKey == goog.Timer.INVALID_TIMEOUT_ID_) {
324
reject(new Error('Failed to schedule timer.'));
325
}
326
})
327
.thenCatch(function(error) {
328
'use strict';
329
// Clear the timer. The most likely reason is "cancel" signal.
330
goog.Timer.clear(timerKey);
331
throw error;
332
});
333
};
334
335