// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * @fileoverview Queue of pending requests from an origin. */ 'use strict'; /** * Represents a queued request. Once given a token, call complete() once the * request is processed (or dropped.) * @interface */ function QueuedRequestToken() {} /** Completes (or cancels) this queued request. */ QueuedRequestToken.prototype.complete = function() {}; /** * @param {!RequestQueue} queue The queue for this request. * @param {function(QueuedRequestToken)} beginCb Called when work may begin on * this request. * @param {RequestToken} opt_prev Previous request in the same queue. * @param {RequestToken} opt_next Next request in the same queue. * @constructor * @implements {QueuedRequestToken} */ function RequestToken(queue, beginCb, opt_prev, opt_next) { /** @private {!RequestQueue} */ this.queue_ = queue; /** @type {function(QueuedRequestToken)} */ this.beginCb = beginCb; /** @type {RequestToken} */ this.prev = null; /** @type {RequestToken} */ this.next = null; /** @private {boolean} */ this.completed_ = false; } /** Completes (or cancels) this queued request. */ RequestToken.prototype.complete = function() { if (this.completed_) { // Either the caller called us more than once, or the timer is firing. // Either way, nothing more to do here. return; } this.completed_ = true; this.queue_.complete(this); }; /** @return {boolean} Whether this token has already completed. */ RequestToken.prototype.completed = function() { return this.completed_; }; /** * @constructor */ function RequestQueue() { /** @private {RequestToken} */ this.head_ = null; /** @private {RequestToken} */ this.tail_ = null; } /** * Inserts this token into the queue. * @param {RequestToken} token Queue token * @private */ RequestQueue.prototype.insertToken_ = function(token) { if (this.head_ === null) { this.head_ = token; this.tail_ = token; } else { if (!this.tail_) throw 'Non-empty list missing tail'; this.tail_.next = token; token.prev = this.tail_; this.tail_ = token; } }; /** * Removes this token from the queue. * @param {RequestToken} token Queue token * @private */ RequestQueue.prototype.removeToken_ = function(token) { if (token.next) { token.next.prev = token.prev; } if (token.prev) { token.prev.next = token.next; } if (this.head_ === token && this.tail_ === token) { this.head_ = this.tail_ = null; } else { if (this.head_ === token) { this.head_ = token.next; this.head_.prev = null; } if (this.tail_ === token) { this.tail_ = token.prev; this.tail_.next = null; } } token.prev = token.next = null; }; /** * Completes this token's request, and begins the next queued request, if one * exists. * @param {RequestToken} token Queue token */ RequestQueue.prototype.complete = function(token) { var next = token.next; this.removeToken_(token); if (next) { next.beginCb(next); } }; /** @return {boolean} Whether this queue is empty. */ RequestQueue.prototype.empty = function() { return this.head_ === null; }; /** * Queues this request, and, if it's the first request, begins work on it. * @param {function(QueuedRequestToken)} beginCb Called when work begins on this * request. * @param {Countdown} timer Countdown timer * @return {QueuedRequestToken} A token for the request. */ RequestQueue.prototype.queueRequest = function(beginCb, timer) { var startNow = this.empty(); var token = new RequestToken(this, beginCb); // Clone the timer to set a callback on it, which will ensure complete() is // eventually called, even if the caller never gets around to it. timer.clone(token.complete.bind(token)); this.insertToken_(token); if (startNow) { window.setTimeout(function() { if (!token.completed()) { token.beginCb(token); } }, 0); } return token; }; /** * @constructor */ function OriginKeyedRequestQueue() { /** @private {Object.} */ this.requests_ = {}; } /** * Queues this request, and, if it's the first request, begins work on it. * @param {string} appId Application Id * @param {string} origin Request origin * @param {function(QueuedRequestToken)} beginCb Called when work begins on this * request. * @param {Countdown} timer Countdown timer * @return {QueuedRequestToken} A token for the request. */ OriginKeyedRequestQueue.prototype.queueRequest = function(appId, origin, beginCb, timer) { var key = appId + origin; if (!this.requests_.hasOwnProperty(key)) { this.requests_[key] = new RequestQueue(); } var queue = this.requests_[key]; return queue.queueRequest(beginCb, timer); };