michael@0: /* Notes: michael@0: - All times are expressed in milliseconds in this test suite. michael@0: - Test harness code is at the end of this file. michael@0: - We generate only one request at a time, to avoid overloading the HTTP michael@0: request handlers. michael@0: */ michael@0: michael@0: var inWorker = false; michael@0: try { michael@0: inWorker = !(self instanceof Window); michael@0: } catch (e) { michael@0: inWorker = true; michael@0: } michael@0: michael@0: function message(data) { michael@0: if (inWorker) michael@0: self.postMessage(data); michael@0: else michael@0: self.postMessage(data, "*"); michael@0: } michael@0: michael@0: function is(got, expected, msg) { michael@0: var obj = {}; michael@0: obj.type = "is"; michael@0: obj.got = got; michael@0: obj.expected = expected; michael@0: obj.msg = msg; michael@0: michael@0: message(obj); michael@0: } michael@0: michael@0: function ok(bool, msg) { michael@0: var obj = {}; michael@0: obj.type = "ok"; michael@0: obj.bool = bool; michael@0: obj.msg = msg; michael@0: michael@0: message(obj); michael@0: } michael@0: michael@0: /** michael@0: * Generate and track results from a XMLHttpRequest with regards to timeouts. michael@0: * michael@0: * @param {String} id The test description. michael@0: * @param {Number} timeLimit The initial setting for the request timeout. michael@0: * @param {Number} resetAfter (Optional) The time after sending the request, to michael@0: * reset the timeout. michael@0: * @param {Number} resetTo (Optional) The delay to reset the timeout to. michael@0: * michael@0: * @note The actual testing takes place in handleEvent(event). michael@0: * The requests are generated in startXHR(). michael@0: * michael@0: * @note If resetAfter and resetTo are omitted, only the initial timeout setting michael@0: * applies. michael@0: * michael@0: * @constructor michael@0: * @implements DOMEventListener michael@0: */ michael@0: function RequestTracker(async, id, timeLimit /*[, resetAfter, resetTo]*/) { michael@0: this.async = async; michael@0: this.id = id; michael@0: this.timeLimit = timeLimit; michael@0: michael@0: if (arguments.length > 3) { michael@0: this.mustReset = true; michael@0: this.resetAfter = arguments[3]; michael@0: this.resetTo = arguments[4]; michael@0: } michael@0: michael@0: this.hasFired = false; michael@0: } michael@0: RequestTracker.prototype = { michael@0: /** michael@0: * Start the XMLHttpRequest! michael@0: */ michael@0: startXHR: function() { michael@0: var req = new XMLHttpRequest(); michael@0: this.request = req; michael@0: req.open("GET", "file_XHR_timeout.sjs", this.async); michael@0: var me = this; michael@0: function handleEvent(e) { return me.handleEvent(e); }; michael@0: req.onerror = handleEvent; michael@0: req.onload = handleEvent; michael@0: req.onabort = handleEvent; michael@0: req.ontimeout = handleEvent; michael@0: michael@0: req.timeout = this.timeLimit; michael@0: michael@0: if (this.mustReset) { michael@0: var resetTo = this.resetTo; michael@0: self.setTimeout(function() { michael@0: req.timeout = resetTo; michael@0: }, this.resetAfter); michael@0: } michael@0: michael@0: req.send(null); michael@0: }, michael@0: michael@0: /** michael@0: * Get a message describing this test. michael@0: * michael@0: * @returns {String} The test description. michael@0: */ michael@0: getMessage: function() { michael@0: var rv = this.id + ", "; michael@0: if (this.mustReset) { michael@0: rv += "original timeout at " + this.timeLimit + ", "; michael@0: rv += "reset at " + this.resetAfter + " to " + this.resetTo; michael@0: } michael@0: else { michael@0: rv += "timeout scheduled at " + this.timeLimit; michael@0: } michael@0: return rv; michael@0: }, michael@0: michael@0: /** michael@0: * Check the event received, and if it's the right (and only) one we get. michael@0: * michael@0: * @param {DOMProgressEvent} evt An event of type "load" or "timeout". michael@0: */ michael@0: handleEvent: function(evt) { michael@0: if (this.hasFired) { michael@0: ok(false, "Only one event should fire: " + this.getMessage()); michael@0: return; michael@0: } michael@0: this.hasFired = true; michael@0: michael@0: var type = evt.type, expectedType; michael@0: // The XHR responds after 3000 milliseconds with a load event. michael@0: var timeLimit = this.mustReset && (this.resetAfter < Math.min(3000, this.timeLimit)) ? michael@0: this.resetTo : michael@0: this.timeLimit; michael@0: if ((timeLimit == 0) || (timeLimit >= 3000)) { michael@0: expectedType = "load"; michael@0: } michael@0: else { michael@0: expectedType = "timeout"; michael@0: } michael@0: is(type, expectedType, this.getMessage()); michael@0: TestCounter.testComplete(); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * Generate and track XMLHttpRequests which will have abort() called on. michael@0: * michael@0: * @param shouldAbort {Boolean} True if we should call abort at all. michael@0: * @param abortDelay {Number} The time in ms to wait before calling abort(). michael@0: */ michael@0: function AbortedRequest(shouldAbort, abortDelay) { michael@0: this.shouldAbort = shouldAbort; michael@0: this.abortDelay = abortDelay; michael@0: this.hasFired = false; michael@0: } michael@0: AbortedRequest.prototype = { michael@0: /** michael@0: * Start the XMLHttpRequest! michael@0: */ michael@0: startXHR: function() { michael@0: var req = new XMLHttpRequest(); michael@0: this.request = req; michael@0: req.open("GET", "file_XHR_timeout.sjs"); michael@0: var me = this; michael@0: function handleEvent(e) { return me.handleEvent(e); }; michael@0: req.onerror = handleEvent; michael@0: req.onload = handleEvent; michael@0: req.onabort = handleEvent; michael@0: req.ontimeout = handleEvent; michael@0: michael@0: req.timeout = 2000; michael@0: var _this = this; michael@0: michael@0: function abortReq() { michael@0: req.abort(); michael@0: } michael@0: michael@0: if (!this.shouldAbort) { michael@0: self.setTimeout(function() { michael@0: try { michael@0: _this.noEventsFired(); michael@0: } michael@0: catch (e) { michael@0: ok(false, "Unexpected error: " + e); michael@0: TestCounter.testComplete(); michael@0: } michael@0: }, 5000); michael@0: } michael@0: else { michael@0: // Abort events can only be triggered on sent requests. michael@0: req.send(); michael@0: if (this.abortDelay == -1) { michael@0: abortReq(); michael@0: } michael@0: else { michael@0: self.setTimeout(abortReq, this.abortDelay); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Ensure that no events fired at all, especially not our timeout event. michael@0: */ michael@0: noEventsFired: function() { michael@0: ok(!this.hasFired, "No events should fire for an unsent, unaborted request"); michael@0: // We're done; if timeout hasn't fired by now, it never will. michael@0: TestCounter.testComplete(); michael@0: }, michael@0: michael@0: /** michael@0: * Get a message describing this test. michael@0: * michael@0: * @returns {String} The test description. michael@0: */ michael@0: getMessage: function() { michael@0: return "time to abort is " + this.abortDelay + ", timeout set at 2000"; michael@0: }, michael@0: michael@0: /** michael@0: * Check the event received, and if it's the right (and only) one we get. michael@0: * michael@0: * @param {DOMProgressEvent} evt An event of type "load" or "timeout". michael@0: */ michael@0: handleEvent: function(evt) { michael@0: if (this.hasFired) { michael@0: ok(false, "Only abort event should fire: " + this.getMessage()); michael@0: return; michael@0: } michael@0: this.hasFired = true; michael@0: michael@0: var expectedEvent = (this.abortDelay >= 2000) ? "timeout" : "abort"; michael@0: is(evt.type, expectedEvent, this.getMessage()); michael@0: TestCounter.testComplete(); michael@0: } michael@0: }; michael@0: michael@0: var SyncRequestSettingTimeoutAfterOpen = { michael@0: startXHR: function() { michael@0: var pass = false; michael@0: var req = new XMLHttpRequest(); michael@0: req.open("GET", "file_XHR_timeout.sjs", false); michael@0: try { michael@0: req.timeout = 1000; michael@0: } michael@0: catch (e) { michael@0: pass = true; michael@0: } michael@0: ok(pass, "Synchronous XHR must not allow a timeout to be set"); michael@0: TestCounter.testComplete(); michael@0: } michael@0: }; michael@0: michael@0: var SyncRequestSettingTimeoutBeforeOpen = { michael@0: startXHR: function() { michael@0: var pass = false; michael@0: var req = new XMLHttpRequest(); michael@0: req.timeout = 1000; michael@0: try { michael@0: req.open("GET", "file_XHR_timeout.sjs", false); michael@0: } michael@0: catch (e) { michael@0: pass = true; michael@0: } michael@0: ok(pass, "Synchronous XHR must not allow a timeout to be set"); michael@0: TestCounter.testComplete(); michael@0: } michael@0: }; michael@0: michael@0: var TestRequests = [ michael@0: // Simple timeouts. michael@0: new RequestTracker(true, "no time out scheduled, load fires normally", 0), michael@0: new RequestTracker(true, "load fires normally", 5000), michael@0: new RequestTracker(true, "timeout hit before load", 2000), michael@0: michael@0: // Timeouts reset after a certain delay. michael@0: new RequestTracker(true, "load fires normally with no timeout set, twice", 0, 2000, 0), michael@0: new RequestTracker(true, "load fires normally with same timeout set twice", 5000, 2000, 5000), michael@0: new RequestTracker(true, "timeout fires normally with same timeout set twice", 2000, 1000, 2000), michael@0: michael@0: new RequestTracker(true, "timeout disabled after initially set", 5000, 2000, 0), michael@0: new RequestTracker(true, "timeout overrides load after a delay", 5000, 1000, 2000), michael@0: new RequestTracker(true, "timeout enabled after initially disabled", 0, 2000, 5000), michael@0: michael@0: new RequestTracker(true, "timeout set to expiring value after load fires", 5000, 4000, 1000), michael@0: new RequestTracker(true, "timeout set to expired value before load fires", 5000, 2000, 1000), michael@0: new RequestTracker(true, "timeout set to non-expiring value after timeout fires", 1000, 2000, 5000), michael@0: michael@0: // Aborted requests. michael@0: new AbortedRequest(false), michael@0: new AbortedRequest(true, -1), michael@0: new AbortedRequest(true, 5000), michael@0: ]; michael@0: michael@0: var MainThreadTestRequests = [ michael@0: new AbortedRequest(true, 0), michael@0: new AbortedRequest(true, 1000), michael@0: michael@0: // Synchronous requests. michael@0: SyncRequestSettingTimeoutAfterOpen, michael@0: SyncRequestSettingTimeoutBeforeOpen michael@0: ]; michael@0: michael@0: var WorkerThreadTestRequests = [ michael@0: // Simple timeouts. michael@0: new RequestTracker(false, "no time out scheduled, load fires normally", 0), michael@0: new RequestTracker(false, "load fires normally", 5000), michael@0: new RequestTracker(false, "timeout hit before load", 2000), michael@0: michael@0: // Reset timeouts don't make much sense with a sync request ... michael@0: ]; michael@0: michael@0: if (inWorker) { michael@0: TestRequests = TestRequests.concat(WorkerThreadTestRequests); michael@0: } else { michael@0: TestRequests = TestRequests.concat(MainThreadTestRequests); michael@0: } michael@0: michael@0: // This code controls moving from one test to another. michael@0: var TestCounter = { michael@0: testComplete: function() { michael@0: // Allow for the possibility there are other events coming. michael@0: self.setTimeout(function() { michael@0: TestCounter.next(); michael@0: }, 5000); michael@0: }, michael@0: michael@0: next: function() { michael@0: var test = TestRequests.shift(); michael@0: michael@0: if (test) { michael@0: test.startXHR(); michael@0: } michael@0: else { michael@0: message("done"); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: self.addEventListener("message", function (event) { michael@0: if (event.data == "start") { michael@0: TestCounter.next(); michael@0: } michael@0: });