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 }