Home | History | Annotate | Download | only in cryptotoken
      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