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