Index: chrome/browser/resources/cryptotoken/requestqueue.js |
diff --git a/chrome/browser/resources/cryptotoken/requestqueue.js b/chrome/browser/resources/cryptotoken/requestqueue.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..153c8d442219b7074682461c35de3ca901e3b5fe |
--- /dev/null |
+++ b/chrome/browser/resources/cryptotoken/requestqueue.js |
@@ -0,0 +1,181 @@ |
+// Copyright (c) 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. |
+ * |
+ * @author juanlang@google.com (Juan Lang) |
+ */ |
+'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 |
+ * @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 |
+ * @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 |
+ */ |
+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 |
+ * @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.<string, !RequestQueue>} */ |
+ this.requests_ = {}; |
+} |
+ |
+/** |
+ * Queues this request, and, if it's the first request, begins work on it. |
+ * @param {string} appId |
+ * @param {string} origin |
+ * @param {function(QueuedRequestToken)} beginCb Called when work begins on this |
+ * request. |
+ * @param {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); |
+}; |