test_XHR_timeout.js (9841B)
1 /* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */ 2 3 /* Notes: 4 - All times are expressed in milliseconds in this test suite. 5 - Test harness code is at the end of this file. 6 - We generate only one request at a time, to avoid overloading the HTTP 7 request handlers. 8 */ 9 10 var inWorker = false; 11 try { 12 inWorker = !(self instanceof Window); 13 } catch (e) { 14 inWorker = true; 15 } 16 17 function message(data) { 18 if (inWorker) { 19 self.postMessage(data); 20 } else { 21 self.postMessage(data, "*"); 22 } 23 } 24 25 function is(got, expected, msg) { 26 var obj = {}; 27 obj.type = "is"; 28 obj.got = got; 29 obj.expected = expected; 30 obj.msg = msg; 31 32 message(obj); 33 } 34 35 function ok(bool, msg) { 36 var obj = {}; 37 obj.type = "ok"; 38 obj.bool = bool; 39 obj.msg = msg; 40 41 message(obj); 42 } 43 44 /** 45 * Generate and track results from a XMLHttpRequest with regards to timeouts. 46 * 47 * @param {string} id The test description. 48 * @param {number} timeLimit The initial setting for the request timeout. 49 * @param {number} resetAfter (Optional) The time after sending the request, to 50 * reset the timeout. 51 * @param {number} resetTo (Optional) The delay to reset the timeout to. 52 * 53 * Note: The actual testing takes place in handleEvent(event). 54 * The requests are generated in startXHR(). 55 * 56 * Note: If resetAfter and resetTo are omitted, only the initial timeout setting 57 * applies. 58 * 59 * @class 60 * @implements DOMEventListener 61 */ 62 function RequestTracker(async, id, timeLimit /*[, resetAfter, resetTo]*/) { 63 this.async = async; 64 this.id = id; 65 this.timeLimit = timeLimit; 66 67 if (arguments.length > 3) { 68 this.mustReset = true; 69 this.resetAfter = arguments[3]; 70 this.resetTo = arguments[4]; 71 } 72 73 this.hasFired = false; 74 } 75 RequestTracker.prototype = { 76 /** 77 * Start the XMLHttpRequest! 78 */ 79 startXHR() { 80 var req = new XMLHttpRequest(); 81 this.request = req; 82 req.open("GET", "file_XHR_timeout.sjs", this.async); 83 var me = this; 84 function handleEvent(e) { 85 return me.handleEvent(e); 86 } 87 req.onerror = handleEvent; 88 req.onload = handleEvent; 89 req.onabort = handleEvent; 90 req.ontimeout = handleEvent; 91 92 req.timeout = this.timeLimit; 93 94 if (this.mustReset) { 95 var resetTo = this.resetTo; 96 self.setTimeout(function () { 97 req.timeout = resetTo; 98 }, this.resetAfter); 99 } 100 101 var gotException; 102 var expectTimeoutException = 103 !this.async && inWorker && this.timeLimit > 0 && this.timeLimit < 3000; 104 105 try { 106 req.send(null); 107 } catch (e) { 108 gotException = e; 109 if (expectTimeoutException) { 110 ok(e.name == "TimeoutError", "Should be a TimeoutError"); 111 } 112 } 113 114 if (gotException && !expectTimeoutException) { 115 ok(false, `expected no exception, got ${gotException}`); 116 } else if (!gotException && expectTimeoutException) { 117 ok(false, "expected timeout exception"); 118 } 119 }, 120 121 /** 122 * Get a message describing this test. 123 * 124 * @returns {string} The test description. 125 */ 126 getMessage() { 127 var rv = this.id + ", "; 128 if (this.mustReset) { 129 rv += "original timeout at " + this.timeLimit + ", "; 130 rv += "reset at " + this.resetAfter + " to " + this.resetTo; 131 } else { 132 rv += "timeout scheduled at " + this.timeLimit; 133 } 134 return rv; 135 }, 136 137 /** 138 * Check the event received, and if it's the right (and only) one we get. 139 * 140 * @param {DOMProgressEvent} evt An event of type "load" or "timeout". 141 */ 142 handleEvent(evt) { 143 if (this.hasFired) { 144 ok(false, "Only one event should fire: " + this.getMessage()); 145 return; 146 } 147 this.hasFired = true; 148 149 var type = evt.type, 150 expectedType; 151 // The XHR responds after 3000 milliseconds with a load event. 152 var timeLimit = 153 this.mustReset && this.resetAfter < Math.min(3000, this.timeLimit) 154 ? this.resetTo 155 : this.timeLimit; 156 if (timeLimit == 0 || timeLimit >= 3000) { 157 expectedType = "load"; 158 } else { 159 expectedType = "timeout"; 160 } 161 is(type, expectedType, this.getMessage()); 162 TestCounter.testComplete(); 163 }, 164 }; 165 166 /** 167 * Generate and track XMLHttpRequests which will have abort() called on. 168 * 169 * @param shouldAbort {Boolean} True if we should call abort at all. 170 * @param abortDelay {Number} The time in ms to wait before calling abort(). 171 */ 172 function AbortedRequest(shouldAbort, abortDelay) { 173 this.shouldAbort = shouldAbort; 174 this.abortDelay = abortDelay; 175 this.hasFired = false; 176 } 177 AbortedRequest.prototype = { 178 /** 179 * Start the XMLHttpRequest! 180 */ 181 startXHR() { 182 var req = new XMLHttpRequest(); 183 this.request = req; 184 req.open("GET", "file_XHR_timeout.sjs"); 185 var me = this; 186 function handleEvent(e) { 187 return me.handleEvent(e); 188 } 189 req.onerror = handleEvent; 190 req.onload = handleEvent; 191 req.onabort = handleEvent; 192 req.ontimeout = handleEvent; 193 194 req.timeout = 2000; 195 var _this = this; 196 197 function abortReq() { 198 req.abort(); 199 } 200 201 if (!this.shouldAbort) { 202 self.setTimeout(function () { 203 try { 204 _this.noEventsFired(); 205 } catch (e) { 206 ok(false, "Unexpected error: " + e); 207 TestCounter.testComplete(); 208 } 209 }, 5000); 210 } else { 211 // Abort events can only be triggered on sent requests. 212 req.send(); 213 if (this.abortDelay == -1) { 214 abortReq(); 215 } else { 216 self.setTimeout(abortReq, this.abortDelay); 217 } 218 } 219 }, 220 221 /** 222 * Ensure that no events fired at all, especially not our timeout event. 223 */ 224 noEventsFired() { 225 ok( 226 !this.hasFired, 227 "No events should fire for an unsent, unaborted request" 228 ); 229 // We're done; if timeout hasn't fired by now, it never will. 230 TestCounter.testComplete(); 231 }, 232 233 /** 234 * Get a message describing this test. 235 * 236 * @returns {string} The test description. 237 */ 238 getMessage() { 239 return "time to abort is " + this.abortDelay + ", timeout set at 2000"; 240 }, 241 242 /** 243 * Check the event received, and if it's the right (and only) one we get. 244 * 245 * @param {DOMProgressEvent} evt An event of type "load" or "timeout". 246 */ 247 handleEvent(evt) { 248 if (this.hasFired) { 249 ok(false, "Only abort event should fire: " + this.getMessage()); 250 return; 251 } 252 this.hasFired = true; 253 254 var expectedEvent = this.abortDelay >= 2000 ? "timeout" : "abort"; 255 is(evt.type, expectedEvent, this.getMessage()); 256 TestCounter.testComplete(); 257 }, 258 }; 259 260 var SyncRequestSettingTimeoutAfterOpen = { 261 startXHR() { 262 var pass = false; 263 var req = new XMLHttpRequest(); 264 req.open("GET", "file_XHR_timeout.sjs", false); 265 try { 266 req.timeout = 1000; 267 } catch (e) { 268 pass = true; 269 } 270 ok(pass, "Synchronous XHR must not allow a timeout to be set"); 271 TestCounter.testComplete(); 272 }, 273 }; 274 275 var SyncRequestSettingTimeoutBeforeOpen = { 276 startXHR() { 277 var pass = false; 278 var req = new XMLHttpRequest(); 279 req.timeout = 1000; 280 try { 281 req.open("GET", "file_XHR_timeout.sjs", false); 282 } catch (e) { 283 pass = true; 284 } 285 ok(pass, "Synchronous XHR must not allow a timeout to be set"); 286 TestCounter.testComplete(); 287 }, 288 }; 289 290 var TestRequests = [ 291 // Simple timeouts. 292 new RequestTracker(true, "no time out scheduled, load fires normally", 0), 293 new RequestTracker(true, "load fires normally", 5000), 294 new RequestTracker(true, "timeout hit before load", 2000), 295 296 // Timeouts reset after a certain delay. 297 new RequestTracker( 298 true, 299 "load fires normally with no timeout set, twice", 300 0, 301 2000, 302 0 303 ), 304 new RequestTracker( 305 true, 306 "load fires normally with same timeout set twice", 307 5000, 308 2000, 309 5000 310 ), 311 new RequestTracker( 312 true, 313 "timeout fires normally with same timeout set twice", 314 2000, 315 1000, 316 2000 317 ), 318 319 new RequestTracker( 320 true, 321 "timeout disabled after initially set", 322 5000, 323 2000, 324 0 325 ), 326 new RequestTracker( 327 true, 328 "timeout overrides load after a delay", 329 5000, 330 1000, 331 2000 332 ), 333 new RequestTracker( 334 true, 335 "timeout enabled after initially disabled", 336 0, 337 2000, 338 5000 339 ), 340 341 new RequestTracker( 342 true, 343 "timeout set to expiring value after load fires", 344 5000, 345 4000, 346 1000 347 ), 348 new RequestTracker( 349 true, 350 "timeout set to expired value before load fires", 351 5000, 352 2000, 353 1000 354 ), 355 new RequestTracker( 356 true, 357 "timeout set to non-expiring value after timeout fires", 358 1000, 359 2000, 360 5000 361 ), 362 363 // Aborted requests. 364 new AbortedRequest(false), 365 new AbortedRequest(true, -1), 366 new AbortedRequest(true, 5000), 367 ]; 368 369 var MainThreadTestRequests = [ 370 new AbortedRequest(true, 0), 371 new AbortedRequest(true, 1000), 372 373 // Synchronous requests. 374 SyncRequestSettingTimeoutAfterOpen, 375 SyncRequestSettingTimeoutBeforeOpen, 376 ]; 377 378 var WorkerThreadTestRequests = [ 379 // Simple timeouts. 380 new RequestTracker(false, "no time out scheduled, load fires normally", 0), 381 new RequestTracker(false, "load fires normally", 5000), 382 new RequestTracker(false, "timeout hit before load", 2000), 383 384 // Reset timeouts don't make much sense with a sync request ... 385 ]; 386 387 if (inWorker) { 388 TestRequests = TestRequests.concat(WorkerThreadTestRequests); 389 } else { 390 TestRequests = TestRequests.concat(MainThreadTestRequests); 391 } 392 393 // This code controls moving from one test to another. 394 var TestCounter = { 395 testComplete() { 396 // Allow for the possibility there are other events coming. 397 self.setTimeout(function () { 398 TestCounter.next(); 399 }, 5000); 400 }, 401 402 next() { 403 var test = TestRequests.shift(); 404 405 if (test) { 406 test.startXHR(); 407 } else { 408 message("done"); 409 } 410 }, 411 }; 412 413 self.addEventListener("message", function (event) { 414 if (event.data == "start") { 415 TestCounter.next(); 416 } 417 });