1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 /** 6 * @fileoverview Provides a countdown-based timer implementation. 7 */ 8 'use strict'; 9 10 /** 11 * Constructs a new timer. The timer has a very limited resolution, and does 12 * not attempt to be millisecond accurate. Its intended use is as a 13 * low-precision timer that pauses while debugging. 14 * @param {number=} timeoutMillis how long, in milliseconds, the countdown 15 * lasts. 16 * @param {Function=} cb called back when the countdown expires. 17 * @constructor 18 * @implements {Countdown} 19 */ 20 function CountdownTimer(timeoutMillis, cb) { 21 this.remainingMillis = 0; 22 this.setTimeout(timeoutMillis || 0, cb); 23 } 24 25 /** Timer interval */ 26 CountdownTimer.TIMER_INTERVAL_MILLIS = 200; 27 28 /** 29 * Sets a new timeout for this timer. Only possible if the timer is not 30 * currently active. 31 * @param {number} timeoutMillis how long, in milliseconds, the countdown lasts. 32 * @param {Function=} cb called back when the countdown expires. 33 * @return {boolean} whether the timeout could be set. 34 */ 35 CountdownTimer.prototype.setTimeout = function(timeoutMillis, cb) { 36 if (this.timeoutId) 37 return false; 38 if (!timeoutMillis || timeoutMillis < 0) 39 return false; 40 this.remainingMillis = timeoutMillis; 41 this.cb = cb; 42 if (this.remainingMillis > CountdownTimer.TIMER_INTERVAL_MILLIS) { 43 this.timeoutId = 44 window.setInterval(this.timerTick.bind(this), 45 CountdownTimer.TIMER_INTERVAL_MILLIS); 46 } else { 47 // Set a one-shot timer for the last interval. 48 this.timeoutId = 49 window.setTimeout(this.timerTick.bind(this), this.remainingMillis); 50 } 51 return true; 52 }; 53 54 /** Clears this timer's timeout. Timers that are cleared become expired. */ 55 CountdownTimer.prototype.clearTimeout = function() { 56 if (this.timeoutId) { 57 window.clearTimeout(this.timeoutId); 58 this.timeoutId = undefined; 59 } 60 this.remainingMillis = 0; 61 }; 62 63 /** 64 * @return {number} how many milliseconds are remaining until the timer expires. 65 */ 66 CountdownTimer.prototype.millisecondsUntilExpired = function() { 67 return this.remainingMillis > 0 ? this.remainingMillis : 0; 68 }; 69 70 /** @return {boolean} whether the timer has expired. */ 71 CountdownTimer.prototype.expired = function() { 72 return this.remainingMillis <= 0; 73 }; 74 75 /** 76 * Constructs a new clone of this timer, while overriding its callback. 77 * @param {Function=} cb callback for new timer. 78 * @return {!Countdown} new clone. 79 */ 80 CountdownTimer.prototype.clone = function(cb) { 81 return new CountdownTimer(this.remainingMillis, cb); 82 }; 83 84 /** Timer callback. */ 85 CountdownTimer.prototype.timerTick = function() { 86 this.remainingMillis -= CountdownTimer.TIMER_INTERVAL_MILLIS; 87 if (this.expired()) { 88 window.clearTimeout(this.timeoutId); 89 this.timeoutId = undefined; 90 if (this.cb) { 91 this.cb(); 92 } 93 } 94 }; 95 96 /** 97 * A factory for creating CountdownTimers. 98 * @constructor 99 * @implements {CountdownFactory} 100 */ 101 function CountdownTimerFactory() { 102 } 103 104 /** 105 * Creates a new timer. 106 * @param {number} timeoutMillis How long, in milliseconds, the countdown lasts. 107 * @param {function()=} opt_cb Called back when the countdown expires. 108 * @return {!Countdown} The timer. 109 */ 110 CountdownTimerFactory.prototype.createTimer = 111 function(timeoutMillis, opt_cb) { 112 return new CountdownTimer(timeoutMillis, opt_cb); 113 }; 114 115 /** 116 * Minimum timeout attenuation, below which a response couldn't be reasonably 117 * guaranteed, in seconds. 118 * @const 119 */ 120 var MINIMUM_TIMEOUT_ATTENUATION_SECONDS = 0.5; 121 122 /** 123 * @param {number} timeoutSeconds Timeout value in seconds. 124 * @return {number} The timeout value, attenuated to ensure a response can be 125 * given before the timeout's expiration. 126 * @private 127 */ 128 function attenuateTimeoutInSeconds_(timeoutSeconds) { 129 if (timeoutSeconds < MINIMUM_TIMEOUT_ATTENUATION_SECONDS) 130 return 0; 131 return timeoutSeconds - MINIMUM_TIMEOUT_ATTENUATION_SECONDS; 132 } 133 134 /** 135 * Default request timeout when none is present in the request, in seconds. 136 * @const 137 */ 138 var DEFAULT_REQUEST_TIMEOUT_SECONDS = 30; 139 140 /** 141 * Creates a new countdown from the given request using the given timer factory, 142 * attenuated to ensure a response is given prior to the countdown's expiration. 143 * @param {CountdownFactory} timerFactory The factory to use. 144 * @param {Object} request The request containing the timeout. 145 * @param {number=} opt_defaultTimeoutSeconds 146 * @return {!Countdown} A countdown timer. 147 */ 148 function createTimerForRequest(timerFactory, request, 149 opt_defaultTimeoutSeconds) { 150 var timeoutValueSeconds; 151 if (request.hasOwnProperty('timeoutSeconds')) { 152 timeoutValueSeconds = request['timeoutSeconds']; 153 } else if (request.hasOwnProperty('timeout')) { 154 timeoutValueSeconds = request['timeout']; 155 } else if (opt_defaultTimeoutSeconds !== undefined) { 156 timeoutValueSeconds = opt_defaultTimeoutSeconds; 157 } else { 158 timeoutValueSeconds = DEFAULT_REQUEST_TIMEOUT_SECONDS; 159 } 160 timeoutValueSeconds = attenuateTimeoutInSeconds_(timeoutValueSeconds); 161 return timerFactory.createTimer(timeoutValueSeconds * 1000); 162 } 163