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