Bug 498998: Implement XHR timeout in Workers. r=sicking

This commit is contained in:
Kyle Huey 2012-03-12 17:00:46 -07:00
Родитель ec9c9696c3
Коммит 621eeb9127
8 изменённых файлов: 244 добавлений и 39 удалений

Просмотреть файл

@ -26,6 +26,34 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=525816
<script class="testbody"
type="text/javascript"
src="test_XHR_timeout.js"></script>
<script type="text/javascript">
window.addEventListener("message", function (event) {
if (event.data == "done") {
SimpleTest.finish();
return;
}
if (event.data == "start") {
return;
}
if (event.data.type == "is") {
SimpleTest.is(event.data.got, event.data.expected, event.data.msg);
return;
}
if (event.data.type == "ok") {
SimpleTest.ok(event.data.bool, event.data.msg);
return;
}
});
// Final test harness setup and launch.
(function() {
SimpleTest.waitForExplicitFinish();
SimpleTest.requestLongerTimeout(TestRequests.length);
var msg = "This test will take approximately " + (TestRequests.length * 10)
msg += " seconds to complete, at most.";
document.getElementById("content").firstChild.nodeValue = msg;
window.postMessage("start", "*");
})();
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -5,6 +5,32 @@
request handlers.
*/
var inWorker = false;
try {
inWorker = !(self instanceof Window);
} catch (e) {
inWorker = true;
}
function is(got, expected, msg) {
var obj = {};
obj.type = "is";
obj.got = got;
obj.expected = expected;
obj.msg = msg;
self.postMessage(obj, "*");
}
function ok(bool, msg) {
var obj = {};
obj.type = "ok";
obj.bool = bool;
obj.msg = msg;
self.postMessage(obj, "*");
}
/**
* Generate and track results from a XMLHttpRequest with regards to timeouts.
*
@ -23,14 +49,15 @@
* @constructor
* @implements DOMEventListener
*/
function RequestTracker(id, timeLimit /*[, resetAfter, resetTo]*/) {
function RequestTracker(async, id, timeLimit /*[, resetAfter, resetTo]*/) {
this.async = async;
this.id = id;
this.timeLimit = timeLimit;
if (arguments.length > 2) {
if (arguments.length > 3) {
this.mustReset = true;
this.resetAfter = arguments[2];
this.resetTo = arguments[3];
this.resetAfter = arguments[3];
this.resetTo = arguments[4];
}
this.hasFired = false;
@ -42,7 +69,7 @@ RequestTracker.prototype = {
startXHR: function() {
var req = new XMLHttpRequest();
this.request = req;
req.open("GET", "file_XHR_timeout.sjs");
req.open("GET", "file_XHR_timeout.sjs", this.async);
req.onerror = this;
req.onload = this;
req.onabort = this;
@ -52,7 +79,7 @@ RequestTracker.prototype = {
if (this.mustReset) {
var resetTo = this.resetTo;
window.setTimeout(function() {
self.setTimeout(function() {
req.timeout = resetTo;
}, this.resetAfter);
}
@ -137,7 +164,7 @@ AbortedRequest.prototype = {
}
if (!this.shouldAbort) {
window.setTimeout(function() {
self.setTimeout(function() {
try {
_this.noEventsFired();
}
@ -154,7 +181,7 @@ AbortedRequest.prototype = {
abortReq();
}
else {
window.setTimeout(abortReq, this.abortDelay);
self.setTimeout(abortReq, this.abortDelay);
}
}
},
@ -229,22 +256,22 @@ var SyncRequestSettingTimeoutBeforeOpen = {
var TestRequests = [
// Simple timeouts.
new RequestTracker("no time out scheduled, load fires normally", 0),
new RequestTracker("load fires normally", 5000),
new RequestTracker("timeout hit before load", 2000),
new RequestTracker(true, "no time out scheduled, load fires normally", 0),
new RequestTracker(true, "load fires normally", 5000),
new RequestTracker(true, "timeout hit before load", 2000),
// Timeouts reset after a certain delay.
new RequestTracker("load fires normally with no timeout set, twice", 0, 2000, 0),
new RequestTracker("load fires normally with same timeout set twice", 5000, 2000, 5000),
new RequestTracker("timeout fires normally with same timeout set twice", 2000, 1000, 2000),
new RequestTracker(true, "load fires normally with no timeout set, twice", 0, 2000, 0),
new RequestTracker(true, "load fires normally with same timeout set twice", 5000, 2000, 5000),
new RequestTracker(true, "timeout fires normally with same timeout set twice", 2000, 1000, 2000),
new RequestTracker("timeout disabled after initially set", 5000, 2000, 0),
new RequestTracker("timeout overrides load after a delay", 5000, 1000, 2000),
new RequestTracker("timeout enabled after initially disabled", 0, 2000, 5000),
new RequestTracker(true, "timeout disabled after initially set", 5000, 2000, 0),
new RequestTracker(true, "timeout overrides load after a delay", 5000, 1000, 2000),
new RequestTracker(true, "timeout enabled after initially disabled", 0, 2000, 5000),
new RequestTracker("timeout set to expiring value after load fires", 5000, 4000, 1000),
new RequestTracker("timeout set to expired value before load fires", 5000, 2000, 1000),
new RequestTracker("timeout set to non-expiring value after timeout fires", 1000, 2000, 5000),
new RequestTracker(true, "timeout set to expiring value after load fires", 5000, 4000, 1000),
new RequestTracker(true, "timeout set to expired value before load fires", 5000, 2000, 1000),
new RequestTracker(true, "timeout set to non-expiring value after timeout fires", 1000, 2000, 5000),
// Aborted requests.
new AbortedRequest(false),
@ -252,17 +279,34 @@ var TestRequests = [
new AbortedRequest(true, 0),
new AbortedRequest(true, 1000),
new AbortedRequest(true, 5000),
];
var MainThreadTestRequests = [
// Synchronous requests.
SyncRequestSettingTimeoutAfterOpen,
SyncRequestSettingTimeoutBeforeOpen
];
var WorkerThreadTestRequests = [
// Simple timeouts.
new RequestTracker(false, "no time out scheduled, load fires normally", 0),
new RequestTracker(false, "load fires normally", 5000),
new RequestTracker(false, "timeout hit before load", 2000),
// Reset timeouts don't make much sense with a sync request ...
];
if (inWorker) {
TestRequests = TestRequests.concat(WorkerThreadTestRequests);
} else {
TestRequests = TestRequests.concat(MainThreadTestRequests);
}
// This code controls moving from one test to another.
var TestCounter = {
testComplete: function() {
// Allow for the possibility there are other events coming.
window.setTimeout(function() {
self.setTimeout(function() {
TestCounter.next();
}, 5000);
},
@ -274,17 +318,13 @@ var TestCounter = {
test.startXHR();
}
else {
SimpleTest.finish();
self.postMessage("done", "*");
}
}
};
// Final test harness setup and launch.
(function() {
SimpleTest.waitForExplicitFinish();
SimpleTest.requestLongerTimeout(TestRequests.length);
var msg = "This test will take approximately " + (TestRequests.length * 10)
msg += " seconds to complete, at most.";
document.getElementById("content").firstChild.nodeValue = msg;
TestCounter.next();
})();
self.addEventListener("message", function (event) {
if (event.data == "start") {
TestCounter.next();
}
});

Просмотреть файл

@ -435,6 +435,8 @@ ListenerManager::DispatchEvent(JSContext* aCx, JSObject* aTarget,
static const char sHandleEventChars[] = "handleEvent";
JSObject* thisObj = aTarget;
JSBool hasHandleEvent;
if (!JS_HasProperty(aCx, listenerObj, sHandleEventChars, &hasHandleEvent)) {
if (!JS_ReportPendingException(aCx)) {
@ -450,11 +452,13 @@ ListenerManager::DispatchEvent(JSContext* aCx, JSObject* aTarget,
}
continue;
}
thisObj = listenerObj;
}
jsval argv[] = { OBJECT_TO_JSVAL(aEvent) };
jsval rval = JSVAL_VOID;
if (!JS_CallFunctionValue(aCx, aTarget, listenerVal, ArrayLength(argv),
if (!JS_CallFunctionValue(aCx, thisObj, listenerVal, ArrayLength(argv),
argv, &rval)) {
if (!JS_ReportPendingException(aCx)) {
return false;

Просмотреть файл

@ -75,6 +75,7 @@ class XMLHttpRequestUpload : public events::EventTarget
STRING_onloadstart,
STRING_onprogress,
STRING_onloadend,
STRING_ontimeout,
STRING_COUNT
};
@ -239,6 +240,8 @@ JSPropertySpec XMLHttpRequestUpload::sProperties[] = {
GetEventListener, SetEventListener },
{ sEventStrings[STRING_onloadend], STRING_onloadend, PROPERTY_FLAGS,
GetEventListener, SetEventListener },
{ sEventStrings[STRING_ontimeout], STRING_ontimeout, PROPERTY_FLAGS,
GetEventListener, SetEventListener },
{ 0, 0, 0, NULL, NULL }
};
@ -248,7 +251,8 @@ const char* const XMLHttpRequestUpload::sEventStrings[STRING_COUNT] = {
"onload",
"onloadstart",
"onprogress",
"onloadend"
"onloadend",
"ontimeout"
};
class XMLHttpRequest
@ -271,6 +275,7 @@ class XMLHttpRequest
SLOT_withCredentials,
SLOT_upload,
SLOT_responseType,
SLOT_timeout,
SLOT_COUNT
};
@ -292,6 +297,7 @@ class XMLHttpRequest
STRING_onloadstart,
STRING_onprogress,
STRING_onloadend,
STRING_ontimeout,
STRING_COUNT
};
@ -399,6 +405,7 @@ private:
JS_SetReservedSlot(obj, SLOT_withCredentials, JSVAL_FALSE);
JS_SetReservedSlot(obj, SLOT_upload, JSVAL_NULL);
JS_SetReservedSlot(obj, SLOT_responseType, STRING_TO_JSVAL(textStr));
JS_SetReservedSlot(obj, SLOT_timeout, zero);
WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
XMLHttpRequestPrivate* priv = new XMLHttpRequestPrivate(obj, workerPrivate);
@ -528,6 +535,7 @@ private:
IMPL_SETTER(MozBackgroundRequest)
IMPL_SETTER(WithCredentials)
IMPL_SETTER(ResponseType)
IMPL_SETTER(Timeout)
#undef IMPL_SETTER
@ -798,6 +806,8 @@ JSPropertySpec XMLHttpRequest::sProperties[] = {
js_GetterOnlyPropertyStub },
{ "responseType", SLOT_responseType, PROPERTY_FLAGS, GetProperty,
SetResponseType },
{ "timeout", SLOT_timeout, PROPERTY_FLAGS, GetProperty,
SetTimeout },
{ sEventStrings[STRING_onreadystatechange], STRING_onreadystatechange,
PROPERTY_FLAGS, GetEventListener, SetEventListener },
{ sEventStrings[STRING_onabort], STRING_onabort, PROPERTY_FLAGS,
@ -812,6 +822,8 @@ JSPropertySpec XMLHttpRequest::sProperties[] = {
GetEventListener, SetEventListener },
{ sEventStrings[STRING_onloadend], STRING_onloadend, PROPERTY_FLAGS,
GetEventListener, SetEventListener },
{ sEventStrings[STRING_ontimeout], STRING_ontimeout, PROPERTY_FLAGS,
GetEventListener, SetEventListener },
#undef GENERIC_READONLY_PROPERTY
@ -846,7 +858,8 @@ const char* const XMLHttpRequest::sEventStrings[STRING_COUNT] = {
"onload",
"onloadstart",
"onprogress",
"onloadend"
"onloadend",
"ontimeout"
};
// static

Просмотреть файл

@ -266,13 +266,14 @@ enum
STRING_load,
STRING_loadstart,
STRING_progress,
STRING_timeout,
STRING_readystatechange,
STRING_loadend,
STRING_COUNT,
STRING_LAST_XHR = STRING_loadend,
STRING_LAST_EVENTTARGET = STRING_progress
STRING_LAST_EVENTTARGET = STRING_timeout
};
JS_STATIC_ASSERT(STRING_LAST_XHR >= STRING_LAST_EVENTTARGET);
@ -285,6 +286,7 @@ const char* const sEventStrings[] = {
"load",
"loadstart",
"progress",
"timeout",
// nsIXMLHttpRequest event types, supported only by XHR.
"readystatechange",
@ -1007,6 +1009,24 @@ public:
}
};
class SetTimeoutRunnable : public WorkerThreadProxySyncRunnable
{
PRUint32 mTimeout;
public:
SetTimeoutRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
PRUint32 aTimeout)
: WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
mTimeout(aTimeout)
{ }
int
MainThreadRun()
{
return GetDOMExceptionCodeFromResult(mProxy->mXHR->SetTimeout(mTimeout));
}
};
class AbortRunnable : public WorkerThreadProxySyncRunnable
{
public:
@ -1081,15 +1101,18 @@ class OpenRunnable : public WorkerThreadProxySyncRunnable
bool mMultipart;
bool mBackgroundRequest;
bool mWithCredentials;
PRUint32 mTimeout;
public:
OpenRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
const nsCString& aMethod, const nsCString& aURL,
const nsString& aUser, const nsString& aPassword,
bool aMultipart, bool aBackgroundRequest, bool aWithCredentials)
bool aMultipart, bool aBackgroundRequest, bool aWithCredentials,
PRUint32 aTimeout)
: WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mMethod(aMethod),
mURL(aURL), mUser(aUser), mPassword(aPassword), mMultipart(aMultipart),
mBackgroundRequest(aBackgroundRequest), mWithCredentials(aWithCredentials)
mBackgroundRequest(aBackgroundRequest), mWithCredentials(aWithCredentials),
mTimeout(aTimeout)
{ }
int
@ -1134,6 +1157,13 @@ public:
}
}
if (mTimeout) {
rv = mProxy->mXHR->SetTimeout(mTimeout);
if (NS_FAILED(rv)) {
return GetDOMExceptionCodeFromResult(rv);
}
}
mProxy->mPreviousStatusText.Truncate();
NS_ASSERTION(!mProxy->mInOpen, "Reentrancy is bad!");
@ -1487,7 +1517,7 @@ XMLHttpRequestPrivate::XMLHttpRequestPrivate(JSObject* aObj,
WorkerPrivate* aWorkerPrivate)
: mJSObject(aObj), mUploadJSObject(nsnull), mWorkerPrivate(aWorkerPrivate),
mJSObjectRooted(false), mMultipart(false), mBackgroundRequest(false),
mWithCredentials(false), mCanceled(false)
mWithCredentials(false), mCanceled(false), mTimeout(0)
{
mWorkerPrivate->AssertIsOnWorkerThread();
MOZ_COUNT_CTOR(mozilla::dom::workers::xhr::XMLHttpRequestPrivate);
@ -1745,6 +1775,33 @@ XMLHttpRequestPrivate::SetResponseType(JSContext* aCx, jsval aOldVal,
return true;
}
bool
XMLHttpRequestPrivate::SetTimeout(JSContext* aCx, jsval aOldVal, jsval *aVp)
{
mWorkerPrivate->AssertIsOnWorkerThread();
uint32_t timeout;
if (!JS_ValueToECMAUint32(aCx, *aVp, &timeout)) {
return false;
}
mTimeout = timeout;
if (!mProxy) {
// Open may not have been called yet, in which case we'll handle the
// timeout in OpenRunnable.
return true;
}
nsRefPtr<SetTimeoutRunnable> runnable =
new SetTimeoutRunnable(mWorkerPrivate, mProxy, timeout);
if (!runnable->Dispatch(aCx)) {
return false;
}
return true;
}
bool
XMLHttpRequestPrivate::Abort(JSContext* aCx)
{
@ -1854,7 +1911,7 @@ XMLHttpRequestPrivate::Open(JSContext* aCx, JSString* aMethod, JSString* aURL,
nsRefPtr<OpenRunnable> runnable =
new OpenRunnable(mWorkerPrivate, mProxy, NS_ConvertUTF16toUTF8(method),
NS_ConvertUTF16toUTF8(url), user, password, mMultipart,
mBackgroundRequest, mWithCredentials);
mBackgroundRequest, mWithCredentials, mTimeout);
// These were only useful before we had a proxy. From here on out changing
// those values makes no difference.

Просмотреть файл

@ -67,6 +67,7 @@ class XMLHttpRequestPrivate : public events::EventTarget,
bool mBackgroundRequest;
bool mWithCredentials;
bool mCanceled;
PRUint32 mTimeout;
public:
XMLHttpRequestPrivate(JSObject* aObj, WorkerPrivate* aWorkerPrivate);
@ -122,6 +123,9 @@ public:
bool
SetResponseType(JSContext* aCx, jsval aOldVal, jsval *aVp);
bool
SetTimeout(JSContext* aCx, jsval aOldVal, jsval *aVp);
bool
Abort(JSContext* aCx);

Просмотреть файл

@ -122,6 +122,7 @@ _TEST_FILES = \
workersDisabled_worker.js \
test_xhr_implicit_cancel.html \
xhr_implicit_cancel_worker.js \
test_xhr_timeout.html \
$(NULL)
_SUBDIR_TEST_FILES = \

Просмотреть файл

@ -0,0 +1,58 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=498998
-->
<head>
<title>Test for Bug 498998</title>
<script type="application/javascript"
src="/MochiKit/MochiKit.js"></script>
<script type="application/javascript"
src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet"
type="text/css"
href="/tests/SimpleTest/test.css">
</head>
<body>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=498998"
>Mozilla Bug 498998 (Worker XMLHttpRequest timeout)</a>
<p id="display"></p>
<div id="content">
This test takes over 1 minute to run, probably over 2 minutes.
</div>
<pre id="test">
<script type="text/javascript">
var worker = new Worker("../../../content/base/test/test_XHR_timeout.js");
worker.addEventListener("message", function (event) {
if (event.data == "done") {
SimpleTest.finish();
return;
}
if (event.data == "start") {
return;
}
if (event.data.type == "is") {
SimpleTest.is(event.data.got, event.data.expected, event.data.msg);
return;
}
if (event.data.type == "ok") {
SimpleTest.ok(event.data.bool, event.data.msg);
return;
}
});
// Final test harness setup and launch.
(function() {
SimpleTest.waitForExplicitFinish();
SimpleTest.requestLongerTimeout(20);
var msg = "This test will take approximately " + (20 * 10)
msg += " seconds to complete, at most.";
document.getElementById("content").firstChild.nodeValue = msg;
worker.postMessage("start");
})();
</script>
</pre>
</body>
</html>