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 Queue of pending requests from an origin. 7 */ 8 'use strict'; 9 10 /** 11 * Represents a queued request. Once given a token, call complete() once the 12 * request is processed (or dropped.) 13 * @interface 14 */ 15 function QueuedRequestToken() {} 16 17 /** Completes (or cancels) this queued request. */ 18 QueuedRequestToken.prototype.complete = function() {}; 19 20 /** 21 * @param {!RequestQueue} queue The queue for this request. 22 * @param {function(QueuedRequestToken)} beginCb Called when work may begin on 23 * this request. 24 * @param {RequestToken} opt_prev Previous request in the same queue. 25 * @param {RequestToken} opt_next Next request in the same queue. 26 * @constructor 27 * @implements {QueuedRequestToken} 28 */ 29 function RequestToken(queue, beginCb, opt_prev, opt_next) { 30 /** @private {!RequestQueue} */ 31 this.queue_ = queue; 32 /** @type {function(QueuedRequestToken)} */ 33 this.beginCb = beginCb; 34 /** @type {RequestToken} */ 35 this.prev = null; 36 /** @type {RequestToken} */ 37 this.next = null; 38 /** @private {boolean} */ 39 this.completed_ = false; 40 } 41 42 /** Completes (or cancels) this queued request. */ 43 RequestToken.prototype.complete = function() { 44 if (this.completed_) { 45 // Either the caller called us more than once, or the timer is firing. 46 // Either way, nothing more to do here. 47 return; 48 } 49 this.completed_ = true; 50 this.queue_.complete(this); 51 }; 52 53 /** @return {boolean} Whether this token has already completed. */ 54 RequestToken.prototype.completed = function() { 55 return this.completed_; 56 }; 57 58 /** 59 * @constructor 60 */ 61 function RequestQueue() { 62 /** @private {RequestToken} */ 63 this.head_ = null; 64 /** @private {RequestToken} */ 65 this.tail_ = null; 66 } 67 68 /** 69 * Inserts this token into the queue. 70 * @param {RequestToken} token Queue token 71 * @private 72 */ 73 RequestQueue.prototype.insertToken_ = function(token) { 74 if (this.head_ === null) { 75 this.head_ = token; 76 this.tail_ = token; 77 } else { 78 if (!this.tail_) throw 'Non-empty list missing tail'; 79 this.tail_.next = token; 80 token.prev = this.tail_; 81 this.tail_ = token; 82 } 83 }; 84 85 /** 86 * Removes this token from the queue. 87 * @param {RequestToken} token Queue token 88 * @private 89 */ 90 RequestQueue.prototype.removeToken_ = function(token) { 91 if (token.next) { 92 token.next.prev = token.prev; 93 } 94 if (token.prev) { 95 token.prev.next = token.next; 96 } 97 if (this.head_ === token && this.tail_ === token) { 98 this.head_ = this.tail_ = null; 99 } else { 100 if (this.head_ === token) { 101 this.head_ = token.next; 102 this.head_.prev = null; 103 } 104 if (this.tail_ === token) { 105 this.tail_ = token.prev; 106 this.tail_.next = null; 107 } 108 } 109 token.prev = token.next = null; 110 }; 111 112 /** 113 * Completes this token's request, and begins the next queued request, if one 114 * exists. 115 * @param {RequestToken} token Queue token 116 */ 117 RequestQueue.prototype.complete = function(token) { 118 var next = token.next; 119 this.removeToken_(token); 120 if (next) { 121 next.beginCb(next); 122 } 123 }; 124 125 /** @return {boolean} Whether this queue is empty. */ 126 RequestQueue.prototype.empty = function() { 127 return this.head_ === null; 128 }; 129 130 /** 131 * Queues this request, and, if it's the first request, begins work on it. 132 * @param {function(QueuedRequestToken)} beginCb Called when work begins on this 133 * request. 134 * @param {Countdown} timer Countdown timer 135 * @return {QueuedRequestToken} A token for the request. 136 */ 137 RequestQueue.prototype.queueRequest = function(beginCb, timer) { 138 var startNow = this.empty(); 139 var token = new RequestToken(this, beginCb); 140 // Clone the timer to set a callback on it, which will ensure complete() is 141 // eventually called, even if the caller never gets around to it. 142 timer.clone(token.complete.bind(token)); 143 this.insertToken_(token); 144 if (startNow) { 145 window.setTimeout(function() { 146 if (!token.completed()) { 147 token.beginCb(token); 148 } 149 }, 0); 150 } 151 return token; 152 }; 153 154 /** 155 * @constructor 156 */ 157 function OriginKeyedRequestQueue() { 158 /** @private {Object.<string, !RequestQueue>} */ 159 this.requests_ = {}; 160 } 161 162 /** 163 * Queues this request, and, if it's the first request, begins work on it. 164 * @param {string} appId Application Id 165 * @param {string} origin Request origin 166 * @param {function(QueuedRequestToken)} beginCb Called when work begins on this 167 * request. 168 * @param {Countdown} timer Countdown timer 169 * @return {QueuedRequestToken} A token for the request. 170 */ 171 OriginKeyedRequestQueue.prototype.queueRequest = 172 function(appId, origin, beginCb, timer) { 173 var key = appId + origin; 174 if (!this.requests_.hasOwnProperty(key)) { 175 this.requests_[key] = new RequestQueue(); 176 } 177 var queue = this.requests_[key]; 178 return queue.queueRequest(beginCb, timer); 179 }; 180