blob: 9c411794373e2804e1790bb174053fa71c595076 [file] [log] [blame]
/* Test imported from Alex Vincent's XHR2 timeout tests, written for Mozilla.
https://hg.mozilla.org/mozilla-central/file/tip/content/base/test/
Released into the public domain, according to
https://bugzilla.mozilla.org/show_bug.cgi?id=525816#c86
*/
/* Notes:
- All times are expressed in milliseconds in this test suite.
- Test harness code is at the end of this file.
- We generate only one request at a time, to avoid overloading the HTTP
request handlers.
*/
var TIME_NORMAL_LOAD = 1000;
var TIME_LATE_TIMEOUT = 800;
var TIME_XHR_LOAD = 600;
var TIME_REGULAR_TIMEOUT = 400;
var TIME_SYNC_TIMEOUT = 200;
var TIME_DELAY = 200;
/*
* This should point to a resource that responds after a delay of TIME_XHR_LOAD milliseconds.
*/
var STALLED_REQUEST_URL = "/resources/load-and-stall.php?name=../../../http/tests/xmlhttprequest/timeout/xmlhttprequest-timeout.js&stallFor=" + TIME_XHR_LOAD/1000 + "&stallAt=0&mimeType=text/plain";
var inWorker = false;
try {
inWorker = !(self instanceof Window);
} catch (e) {
inWorker = true;
}
function message(obj) {
if (inWorker)
self.postMessage(obj);
else
self.postMessage(obj, "*");
}
function is(got, expected, msg) {
var obj = {};
obj.type = "is";
obj.got = got;
obj.expected = expected;
obj.msg = msg;
message(obj);
}
function ok(bool, msg) {
var obj = {};
obj.type = "ok";
obj.bool = bool;
obj.msg = msg;
message(obj);
}
/**
* Generate and track results from a XMLHttpRequest with regards to timeouts.
*
* @param {String} id The test description.
* @param {Number} timeLimit The initial setting for the request timeout.
* @param {Number} resetAfter (Optional) The time after sending the request, to
* reset the timeout.
* @param {Number} resetTo (Optional) The delay to reset the timeout to.
*
* @note The actual testing takes place in handleEvent(event).
* The requests are generated in startXHR().
*
* @note If resetAfter and resetTo are omitted, only the initial timeout setting
* applies.
*
* @constructor
* @implements DOMEventListener
*/
function RequestTracker(async, id, timeLimit /*[, resetAfter, resetTo]*/) {
this.async = async;
this.id = id;
this.timeLimit = timeLimit;
if (arguments.length > 3) {
this.mustReset = true;
this.resetAfter = arguments[3];
this.resetTo = arguments[4];
}
this.hasFired = false;
}
RequestTracker.prototype = {
/**
* Start the XMLHttpRequest!
*/
startXHR: function() {
var req = new XMLHttpRequest();
this.request = req;
req.open("GET", STALLED_REQUEST_URL, this.async);
var me = this;
function handleEvent(e) { return me.handleEvent(e); };
req.onerror = handleEvent;
req.onload = handleEvent;
req.onabort = handleEvent;
req.ontimeout = handleEvent;
req.timeout = this.timeLimit;
if (this.mustReset) {
var resetTo = this.resetTo;
self.setTimeout(function() {
req.timeout = resetTo;
}, this.resetAfter);
}
try {
req.send(null);
}
catch (e) {
// Synchronous case in workers.
ok(!this.async && this.timeLimit < TIME_XHR_LOAD && e.name == "TimeoutError", "Unexpected error: " + e);
TestCounter.testComplete();
}
},
/**
* Get a message describing this test.
*
* @returns {String} The test description.
*/
getMessage: function() {
var rv = this.id + ", ";
if (this.mustReset) {
rv += "original timeout at " + this.timeLimit + ", ";
rv += "reset at " + this.resetAfter + " to " + this.resetTo;
}
else {
rv += "timeout scheduled at " + this.timeLimit;
}
return rv;
},
/**
* Check the event received, and if it's the right (and only) one we get.
*
* @param {DOMProgressEvent} evt An event of type "load" or "timeout".
*/
handleEvent: function(evt) {
if (this.hasFired) {
ok(false, "Only one event should fire: " + this.getMessage());
return;
}
this.hasFired = true;
var type = evt.type, expectedType;
// The XHR responds after TIME_XHR_LOAD milliseconds with a load event.
var timeLimit = this.mustReset && (this.resetAfter < Math.min(TIME_XHR_LOAD, this.timeLimit)) ?
this.resetTo :
this.timeLimit;
if ((timeLimit == 0) || (timeLimit >= TIME_XHR_LOAD)) {
expectedType = "load";
}
else {
expectedType = "timeout";
}
is(type, expectedType, this.getMessage());
TestCounter.testComplete();
}
};
/**
* Generate and track XMLHttpRequests which will timeout or have abort() called
* on.
*
* @param shouldAbort {Boolean} True if we should call send() and then abort()
* at all.
* @param abortDelay {Number} The time in ms to wait before calling abort().
*/
function AbortedRequest(shouldAbort, abortDelay) {
this.shouldAbort = shouldAbort;
this.abortDelay = abortDelay;
this.hasFired = false;
}
AbortedRequest.prototype = {
/**
* Start the XMLHttpRequest!
*/
startXHR: function() {
var req = new XMLHttpRequest();
this.request = req;
req.open("GET", STALLED_REQUEST_URL);
var me = this;
function handleEvent(e) { return me.handleEvent(e); };
req.onerror = handleEvent;
req.onload = handleEvent;
req.onabort = handleEvent;
req.ontimeout = handleEvent;
req.timeout = TIME_REGULAR_TIMEOUT;
function abortReq() {
if (me.abortDelay > TIME_REGULAR_TIMEOUT) {
is(me.request.readyState, XMLHttpRequest.DONE, "XHR must be in DONE state after timeout");
me.ensureTimeoutEventFired();
// req is in DONE state, so, "abort" event won't fire.
req.abort();
} else {
me.ensureNoEventsFired();
// This fires "abort" event.
req.abort();
}
is(me.request.readyState, XMLHttpRequest.UNSENT, "XHR must be in UNSENT state after abort()");
TestCounter.testComplete();
}
if (!this.shouldAbort) {
self.setTimeout(function() {
try {
// send() has not been called. No event should be observed.
me.ensureNoEventsFired();
}
catch (e) {
ok(false, "Unexpected error: " + e);
}
TestCounter.testComplete();
}, TIME_NORMAL_LOAD);
}
else {
// Abort events can only be triggered on sent requests.
req.send();
if (this.abortDelay == -1) {
is(req.readyState, XMLHttpRequest.OPENED, "XHR must be in OPENED state");
// This should fire "abort" event. handleEvent checks that.
req.abort();
TestCounter.testComplete();
}
else {
// Check state of req and call abort() on it after the specified delay.
self.setTimeout(abortReq, this.abortDelay);
}
}
},
/**
* Ensure that no events fired at all, especially not our timeout event.
*/
ensureNoEventsFired: function() {
ok(!this.hasFired, "No events should fire for an unsent, unaborted request");
},
/**
* Ensure that an event fired, our timeout event.
*/
ensureTimeoutEventFired: function() {
ok(this.hasFired && this.eventFired == "timeout", "A timeout event should have fired");
},
/**
* Get a message describing this test.
*
* @returns {String} The test description.
*/
getMessage: function() {
return "time to abort is " + this.abortDelay + ", timeout set at " + TIME_REGULAR_TIMEOUT;
},
/**
* Check the event received, and if it's the right (and only) one we get.
*
* @param {DOMProgressEvent} evt An event of type "load" or "timeout".
*/
handleEvent: function(evt) {
if (this.hasFired) {
ok(false, "Only one event should fire: " + this.getMessage());
return;
}
if (!this.shouldAbort) {
// We don't call send() and abort(). No event should fire.
ok(false, "No event should fire: " + this.getMessage());
return;
}
var expectedEvent;
if (this.abortDelay >= TIME_REGULAR_TIMEOUT) {
// Timeout happens earlier than abort(). abort() will be noop since the
// XHR should be already in DONE state at that point.
expectedEvent = "timeout";
} else {
// abort() will be called earlier than timeout. "abort" event should
// fire.
expectedEvent = "abort";
}
this.hasFired = true;
this.eventFired = evt.type;
is(evt.type, expectedEvent, this.getMessage());
}
};
var SyncRequestSettingTimeoutAfterOpen = {
startXHR: function() {
var pass = false;
var req = new XMLHttpRequest();
req.open("GET", STALLED_REQUEST_URL, false);
try {
req.timeout = TIME_SYNC_TIMEOUT;
}
catch (e) {
pass = true;
}
ok(pass, "Synchronous XHR must not allow a timeout to be set");
TestCounter.testComplete();
}
};
var SyncRequestSettingTimeoutBeforeOpen = {
startXHR: function() {
var pass = false;
var req = new XMLHttpRequest();
req.timeout = TIME_SYNC_TIMEOUT;
try {
req.open("GET", STALLED_REQUEST_URL, false);
}
catch (e) {
pass = true;
}
ok(pass, "Synchronous XHR must not allow a timeout to be set");
TestCounter.testComplete();
}
};
var TestRequestGroups = {
"simple" : [
new RequestTracker(true, "no time out scheduled, load fires normally", 0),
new RequestTracker(true, "load fires normally", TIME_NORMAL_LOAD),
new RequestTracker(true, "timeout hit before load", TIME_REGULAR_TIMEOUT)
],
"twice" : [
new RequestTracker(true, "load fires normally with no timeout set, twice", 0, TIME_REGULAR_TIMEOUT, 0),
new RequestTracker(true, "load fires normally with same timeout set twice", TIME_NORMAL_LOAD, TIME_REGULAR_TIMEOUT, TIME_NORMAL_LOAD),
new RequestTracker(true, "timeout fires normally with same timeout set twice", TIME_REGULAR_TIMEOUT, TIME_DELAY, TIME_REGULAR_TIMEOUT)
],
// FIXME: http://webkit.org/b/98156 - Late updates are not supported yet, these tests are not run.
"overrides" : [
new RequestTracker(true, "timeout disabled after initially set", TIME_NORMAL_LOAD, TIME_REGULAR_TIMEOUT, 0),
new RequestTracker(true, "timeout overrides load after a delay", TIME_NORMAL_LOAD, TIME_DELAY, TIME_REGULAR_TIMEOUT),
new RequestTracker(true, "timeout enabled after initially disabled", 0, TIME_REGULAR_TIMEOUT, TIME_NORMAL_LOAD)
],
"overridesexpires" : [
new RequestTracker(true, "timeout set to expiring value after load fires", TIME_NORMAL_LOAD, TIME_LATE_TIMEOUT, TIME_DELAY),
// FIXME: http://webkit.org/b/98156 - Late updates are not supported yet, this test is not run.
// new RequestTracker(true, "timeout set to expired value before load fires", TIME_NORMAL_LOAD, TIME_REGULAR_TIMEOUT, TIME_DELAY),
new RequestTracker(true, "timeout set to non-expiring value after timeout fires", TIME_DELAY, TIME_REGULAR_TIMEOUT, TIME_NORMAL_LOAD)
],
"aborted" : [
new AbortedRequest(false),
new AbortedRequest(true, -1),
new AbortedRequest(true, TIME_NORMAL_LOAD)
],
"abortedonmain" : [
new AbortedRequest(true, 0),
new AbortedRequest(true, TIME_DELAY)
],
"synconmain" : [
SyncRequestSettingTimeoutAfterOpen,
SyncRequestSettingTimeoutBeforeOpen
],
"synconworker" : [
new RequestTracker(false, "no time out scheduled, load fires normally", 0),
new RequestTracker(false, "load fires normally", TIME_NORMAL_LOAD),
new RequestTracker(false, "timeout hit before load", TIME_REGULAR_TIMEOUT)
]
};
var TestRequests = [];
// This code controls moving from one test to another.
var TestCounter = {
testComplete: function() {
// Allow for the possibility there are other events coming.
self.setTimeout(function() {
TestCounter.next();
}, TIME_NORMAL_LOAD);
},
next: function() {
var test = TestRequests.shift();
if (test) {
test.startXHR();
}
else {
message("done");
}
}
};
self.addEventListener("message", function (event) {
if (event.data.type == "start") {
TestRequests = TestRequestGroups[event.data.group];
TestCounter.next();
}
});