tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

xmlhttprequest-timeout.js (8865B)


      1 /* Test adapted from Alex Vincent's XHR2 timeout tests, written for Mozilla.
      2   https://hg.mozilla.org/mozilla-central/file/tip/content/base/test/
      3   Released into the public domain or under BSD, according to
      4   https://bugzilla.mozilla.org/show_bug.cgi?id=525816#c86
      5 */
      6 
      7 /* Notes:
      8   - All times are expressed in milliseconds in this test suite.
      9   - Test harness code is at the end of this file.
     10   - We generate only one request at a time, to avoid overloading the HTTP
     11   request handlers.
     12 */
     13 
     14 var TIME_NORMAL_LOAD = 5000;
     15 var TIME_LATE_TIMEOUT = 4000;
     16 var TIME_XHR_LOAD = 3000;
     17 var TIME_REGULAR_TIMEOUT = 2000;
     18 var TIME_SYNC_TIMEOUT = 1000;
     19 var TIME_DELAY = 1000;
     20 
     21 /*
     22 * This should point to a resource that responds with a text/plain resource after a delay of TIME_XHR_LOAD milliseconds.
     23 */
     24 var STALLED_REQUEST_URL = "delay.py?ms=" + (TIME_XHR_LOAD);
     25 
     26 var inWorker = false;
     27 try {
     28  inWorker = !(self instanceof Window);
     29 } catch (e) {
     30  inWorker = true;
     31 }
     32 
     33 if (!inWorker)
     34  STALLED_REQUEST_URL = "resources/" + STALLED_REQUEST_URL;
     35 
     36 function message(obj) {
     37  if (inWorker)
     38    self.postMessage(obj);
     39  else
     40    self.postMessage(obj, "*");
     41 }
     42 
     43 function is(got, expected, msg) {
     44  var obj = {};
     45  obj.type = "is";
     46  obj.got = got;
     47  obj.expected = expected;
     48  obj.msg = msg;
     49 
     50  message(obj);
     51 }
     52 
     53 function ok(bool, msg) {
     54  var obj = {};
     55  obj.type = "ok";
     56  obj.bool = bool;
     57  obj.msg = msg;
     58 
     59  message(obj);
     60 }
     61 
     62 /**
     63 * Generate and track results from a XMLHttpRequest with regards to timeouts.
     64 *
     65 * @param {String} id         The test description.
     66 * @param {Number} timeLimit  The initial setting for the request timeout.
     67 * @param {Number} resetAfter (Optional) The time after sending the request, to
     68 *                            reset the timeout.
     69 * @param {Number} resetTo    (Optional) The delay to reset the timeout to.
     70 *
     71 * @note The actual testing takes place in handleEvent(event).
     72 * The requests are generated in startXHR().
     73 *
     74 * @note If resetAfter and resetTo are omitted, only the initial timeout setting
     75 * applies.
     76 *
     77 * @constructor
     78 * @implements DOMEventListener
     79 */
     80 function RequestTracker(async, id, timeLimit /*[, resetAfter, resetTo]*/) {
     81  this.async = async;
     82  this.id = id;
     83  this.timeLimit = timeLimit;
     84 
     85  if (arguments.length > 3) {
     86    this.mustReset  = true;
     87    this.resetAfter = arguments[3];
     88    this.resetTo    = arguments[4];
     89  }
     90 
     91  this.hasFired = false;
     92 }
     93 RequestTracker.prototype = {
     94  /**
     95   * Start the XMLHttpRequest!
     96   */
     97  startXHR: function() {
     98    var req = new XMLHttpRequest();
     99    this.request = req;
    100    req.open("GET", STALLED_REQUEST_URL, this.async);
    101    var me = this;
    102    function handleEvent(e) { return me.handleEvent(e); };
    103    req.onerror = handleEvent;
    104    req.onload = handleEvent;
    105    req.onabort = handleEvent;
    106    req.ontimeout = handleEvent;
    107 
    108    req.timeout = this.timeLimit;
    109 
    110    if (this.mustReset) {
    111      var resetTo = this.resetTo;
    112      self.setTimeout(function() {
    113        req.timeout = resetTo;
    114      }, this.resetAfter);
    115    }
    116 
    117    try {
    118      req.send(null);
    119    }
    120    catch (e) {
    121      // Synchronous case in workers.
    122      ok(!this.async && this.timeLimit < TIME_XHR_LOAD && e.name == "TimeoutError", "Unexpected error: " + e);
    123      TestCounter.testComplete();
    124    }
    125  },
    126 
    127  /**
    128   * Get a message describing this test.
    129   *
    130   * @returns {String} The test description.
    131   */
    132  getMessage: function() {
    133    var rv = this.id + ", ";
    134    if (this.mustReset) {
    135      rv += "original timeout at " + this.timeLimit + ", ";
    136      rv += "reset at " + this.resetAfter + " to " + this.resetTo;
    137    }
    138    else {
    139      rv += "timeout scheduled at " + this.timeLimit;
    140    }
    141    return rv;
    142  },
    143 
    144  /**
    145   * Check the event received, and if it's the right (and only) one we get.
    146   *
    147   * @param {DOMProgressEvent} evt An event of type "load" or "timeout".
    148   */
    149  handleEvent: function(evt) {
    150    if (this.hasFired) {
    151      ok(false, "Only one event should fire: " + this.getMessage());
    152      return;
    153    }
    154    this.hasFired = true;
    155 
    156    var type = evt.type, expectedType;
    157    // The XHR responds after TIME_XHR_LOAD milliseconds with a load event.
    158    var timeLimit = this.mustReset && (this.resetAfter < Math.min(TIME_XHR_LOAD, this.timeLimit)) ?
    159                    this.resetTo :
    160                    this.timeLimit;
    161    if ((timeLimit == 0) || (timeLimit >= TIME_XHR_LOAD)) {
    162      expectedType = "load";
    163    }
    164    else {
    165      expectedType = "timeout";
    166    }
    167    is(type, expectedType, this.getMessage());
    168    TestCounter.testComplete();
    169  }
    170 };
    171 
    172 /**
    173 * Generate and track XMLHttpRequests which will have abort() called on.
    174 *
    175 * @param shouldAbort {Boolean} True if we should call abort at all.
    176 * @param abortDelay  {Number}  The time in ms to wait before calling abort().
    177 */
    178 function AbortedRequest(shouldAbort, id, abortDelay) {
    179  this.shouldAbort = shouldAbort;
    180  this.abortDelay  = abortDelay;
    181  this.hasFired    = false;
    182 }
    183 AbortedRequest.prototype = {
    184  /**
    185   * Start the XMLHttpRequest!
    186   */
    187  startXHR: function() {
    188    var req = new XMLHttpRequest();
    189    this.request = req;
    190    req.open("GET", STALLED_REQUEST_URL);
    191    var _this = this;
    192    function handleEvent(e) { return _this.handleEvent(e); };
    193    req.onerror = handleEvent;
    194    req.onload = handleEvent;
    195    req.onabort = handleEvent;
    196    req.ontimeout = handleEvent;
    197 
    198    req.timeout = TIME_REGULAR_TIMEOUT;
    199 
    200    function abortReq() {
    201      req.abort();
    202    }
    203 
    204    if (!this.shouldAbort) {
    205      self.setTimeout(function() {
    206        try {
    207          _this.noEventsFired();
    208        }
    209        catch (e) {
    210          ok(false, "Unexpected error: " + e);
    211          TestCounter.testComplete();
    212        }
    213      }, TIME_NORMAL_LOAD);
    214    }
    215    else {
    216      // Abort events can only be triggered on sent requests.
    217      req.send();
    218      if (this.abortDelay == -1) {
    219        abortReq();
    220      }
    221      else {
    222        self.setTimeout(abortReq, this.abortDelay);
    223      }
    224    }
    225  },
    226 
    227  /**
    228   * Ensure that no events fired at all, especially not our timeout event.
    229   */
    230  noEventsFired: function() {
    231    ok(!this.hasFired, "No events should fire for an unsent, unaborted request");
    232    // We're done; if timeout hasn't fired by now, it never will.
    233    TestCounter.testComplete();
    234  },
    235 
    236  /**
    237   * Get a message describing this test.
    238   *
    239   * @returns {String} The test description.
    240   */
    241  getMessage: function() {
    242    return "time to abort is " + this.abortDelay + ", timeout set at " + TIME_REGULAR_TIMEOUT;
    243  },
    244 
    245  /**
    246   * Check the event received, and if it's the right (and only) one we get.
    247   *
    248   * WebKit fires abort events even for DONE and UNSENT states, which is
    249   * discussed in http://webkit.org/b/98404
    250   * That's why we chose to accept secondary "abort" events in this test.
    251   *
    252   * @param {DOMProgressEvent} evt An event of type "load" or "timeout".
    253   */
    254  handleEvent: function(evt) {
    255    if (this.hasFired && evt.type != "abort") {
    256      ok(false, "Only abort event should fire: " + this.getMessage());
    257      return;
    258    }
    259 
    260    var expectedEvent = (this.abortDelay >= TIME_REGULAR_TIMEOUT && !this.hasFired) ? "timeout" : "abort";
    261    this.hasFired = true;
    262    is(evt.type, expectedEvent, this.getMessage());
    263    TestCounter.testComplete();
    264  }
    265 };
    266 
    267 function SyncRequestSettingTimeoutAfterOpen() {
    268  this.startXHR = function() {
    269    var pass = false;
    270    var req = new XMLHttpRequest();
    271    req.open("GET", STALLED_REQUEST_URL, false);
    272    try {
    273      req.timeout = TIME_SYNC_TIMEOUT;
    274    }
    275    catch (e) {
    276      pass = true;
    277    }
    278    ok(pass, "Synchronous XHR must not allow a timeout to be set - setting timeout must throw");
    279    TestCounter.testComplete();
    280  };
    281  return this;
    282 };
    283 
    284 function SyncRequestSettingTimeoutBeforeOpen() {
    285  this.startXHR = function() {
    286    var pass = false;
    287    var req = new XMLHttpRequest();
    288    req.timeout = TIME_SYNC_TIMEOUT;
    289    try {
    290      req.open("GET", STALLED_REQUEST_URL, false);
    291    }
    292    catch (e) {
    293      pass = true;
    294    }
    295    ok(pass, "Synchronous XHR must not allow a timeout to be set - calling open() after timeout is set must throw");
    296    TestCounter.testComplete();
    297  }
    298  return this;
    299 };
    300 
    301 var TestRequests = [];
    302 
    303 // This code controls moving from one test to another.
    304 var TestCounter = {
    305  testComplete: function() {
    306    // Allow for the possibility there are other events coming.
    307    self.setTimeout(function() {
    308      TestCounter.next();
    309    }, TIME_NORMAL_LOAD);
    310  },
    311 
    312  next: function() {
    313    var test = TestRequests.shift();
    314 
    315    if (test) {
    316      test.startXHR();
    317    }
    318    else {
    319      message("done");
    320    }
    321  }
    322 };
    323 
    324 function runTestRequests(testRequests) {
    325  if (location.search) {
    326    testRequests = testRequests.filter(test => test[2] == decodeURIComponent(location.search.substr(1)));
    327  }
    328  TestRequests = testRequests.map(test => {
    329    var constructor = test.shift();
    330    return new self[constructor](...test)
    331  });
    332  TestCounter.next();
    333 }