Bug 547399 - 'Workers: Don't let worker messages run if the worker is suspended'. r+sr=jst.

This commit is contained in:
Ben Turner 2010-03-11 12:36:44 -08:00
Родитель fe2a33cdae
Коммит 9554ef498f
6 изменённых файлов: 196 добавлений и 0 удалений

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

@ -933,6 +933,17 @@ public:
return NS_ERROR_ABORT; return NS_ERROR_ABORT;
} }
// If the worker is suspended and we're running on the main thread then we
// can't actually dispatch the event yet. Instead we queue it for whenever
// we resume.
if (mWorker->IsSuspended() && NS_IsMainThread()) {
if (!mWorker->QueueSuspendedRunnable(this)) {
NS_ERROR("Out of memory?!");
return NS_ERROR_ABORT;
}
return NS_OK;
}
nsCOMPtr<nsIDOMEventTarget> target = mToInner ? nsCOMPtr<nsIDOMEventTarget> target = mToInner ?
static_cast<nsDOMWorkerMessageHandler*>(mWorker->GetInnerScope()) : static_cast<nsDOMWorkerMessageHandler*>(mWorker->GetInnerScope()) :
static_cast<nsDOMWorkerMessageHandler*>(mWorker); static_cast<nsDOMWorkerMessageHandler*>(mWorker);
@ -1044,6 +1055,7 @@ nsDOMWorker::~nsDOMWorker()
} }
NS_ASSERTION(!mFeatures.Length(), "Live features!"); NS_ASSERTION(!mFeatures.Length(), "Live features!");
NS_ASSERTION(!mQueuedRunnables.Length(), "Events that never ran!");
nsCOMPtr<nsIThread> mainThread; nsCOMPtr<nsIThread> mainThread;
NS_GetMainThread(getter_AddRefs(mainThread)); NS_GetMainThread(getter_AddRefs(mainThread));
@ -1349,6 +1361,9 @@ nsDOMWorker::Kill()
features[index]->Cancel(); features[index]->Cancel();
} }
// Make sure we kill any queued runnables that we never had a chance to run.
mQueuedRunnables.Clear();
// We no longer need to keep our inner scope. // We no longer need to keep our inner scope.
mInnerScope = nsnull; mInnerScope = nsnull;
mScopeWN = nsnull; mScopeWN = nsnull;
@ -1400,6 +1415,13 @@ nsDOMWorker::Resume()
if (shouldResumeFeatures) { if (shouldResumeFeatures) {
ResumeFeatures(); ResumeFeatures();
} }
// Repost any events that were queued for the main thread while suspended.
PRUint32 count = mQueuedRunnables.Length();
for (PRUint32 index = 0; index < count; index++) {
NS_DispatchToCurrentThread(mQueuedRunnables[index]);
}
mQueuedRunnables.Clear();
} }
PRBool PRBool
@ -1934,6 +1956,13 @@ nsDOMWorker::GetExpirationTime()
} }
#endif #endif
PRBool
nsDOMWorker::QueueSuspendedRunnable(nsIRunnable* aRunnable)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
return mQueuedRunnables.AppendElement(aRunnable) ? PR_TRUE : PR_FALSE;
}
NS_IMETHODIMP NS_IMETHODIMP
nsDOMWorker::AddEventListener(const nsAString& aType, nsDOMWorker::AddEventListener(const nsAString& aType,
nsIDOMEventListener* aListener, nsIDOMEventListener* aListener,

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

@ -64,6 +64,7 @@ class nsDOMWorkerTimeout;
class nsICancelable; class nsICancelable;
class nsIDOMEventListener; class nsIDOMEventListener;
class nsIEventTarget; class nsIEventTarget;
class nsIRunnable;
class nsIScriptGlobalObject; class nsIScriptGlobalObject;
class nsIXPConnectWrappedNative; class nsIXPConnectWrappedNative;
@ -118,6 +119,7 @@ class nsDOMWorker : public nsDOMWorkerMessageHandler,
friend class nsDOMWorkerXHR; friend class nsDOMWorkerXHR;
friend class nsDOMWorkerXHRProxy; friend class nsDOMWorkerXHRProxy;
friend class nsReportErrorRunnable; friend class nsReportErrorRunnable;
friend class nsDOMFireEventRunnable;
friend JSBool DOMWorkerOperationCallback(JSContext* aCx); friend JSBool DOMWorkerOperationCallback(JSContext* aCx);
friend void DOMWorkerErrorReporter(JSContext* aCx, friend void DOMWorkerErrorReporter(JSContext* aCx,
@ -288,6 +290,8 @@ private:
return mLocation; return mLocation;
} }
PRBool QueueSuspendedRunnable(nsIRunnable* aRunnable);
private: private:
// mParent will live as long as mParentWN but only mParentWN will keep the JS // mParent will live as long as mParentWN but only mParentWN will keep the JS
@ -327,6 +331,8 @@ private:
nsCOMPtr<nsIWorkerLocation> mLocation; nsCOMPtr<nsIWorkerLocation> mLocation;
nsTArray<nsCOMPtr<nsIRunnable> > mQueuedRunnables;
PRPackedBool mSuspended; PRPackedBool mSuspended;
PRPackedBool mCompileAttempted; PRPackedBool mCompileAttempted;
}; };

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

@ -82,6 +82,9 @@ _TEST_FILES = \
relativeLoad_import.js \ relativeLoad_import.js \
test_scopeOnerror.html \ test_scopeOnerror.html \
scopeOnerror_worker.js \ scopeOnerror_worker.js \
test_suspend.html \
suspend_iframe.html \
suspend_worker.js \
test_simpleThread.html \ test_simpleThread.html \
simpleThread_worker.js \ simpleThread_worker.js \
test_terminate.html \ test_terminate.html \

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

@ -0,0 +1,40 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for DOM Worker Threads Suspending</title>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<pre id="test">
<div id="output"></div>
<script class="testbody" type="text/javascript">
var output = document.getElementById("output");
var worker;
function terminateWorker() {
if (worker) {
worker.terminate();
worker = null;
}
}
function startWorker(messageCallback, errorCallback) {
worker = new Worker("suspend_worker.js");
worker.onmessage = function(event) {
output.textContent = output.textContent + event.data + "\n";
messageCallback(event.data);
};
worker.onerror = function(event) {
this.terminate();
errorCallback(event.message);
};
}
</script>
</pre>
</body>
</html>

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

@ -0,0 +1,5 @@
var counter = 0;
setInterval(function() {
postMessage(++counter);
}, 100);

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

@ -0,0 +1,113 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for DOM Worker Threads</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
<iframe id="workerFrame" src="suspend_iframe.html" onload="subframeLoaded();">
</iframe>
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
var iframe;
var lastCount;
var suspended;
var resumed;
var interval;
var oldMessageCount;
var waitCount = 0;
function setCachePref(enabled) {
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var prefBranch = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch);
if (enabled) {
prefBranch.setBoolPref("browser.sessionhistory.cache_subframes", true);
}
else {
try {
prefBranch.clearUserPref("browser.sessionhistory.cache_subframes");
} catch (e) { /* Pref didn't exist, meh */ }
}
}
function finishTest() {
setCachePref(false);
iframe.terminateWorker();
SimpleTest.finish();
}
function waitInterval() {
is(iframe.location, "about:blank", "Wrong url!");
is(suspended, true, "Not suspended?");
is(lastCount, oldMessageCount, "Received a message while suspended!");
if (++waitCount == 5) {
clearInterval(interval);
iframe.history.back();
resumed = true;
}
}
function badOnloadCallback() {
ok(false, "iframe didn't go into fastback cache!");
finishTest();
}
function suspendCallback() {
is(iframe.location, "about:blank", "Wrong url!");
is(suspended, true, "Not suspended?");
iframe.onload = badOnloadCallback;
oldMessageCount = lastCount;
interval = setInterval(waitInterval, 1000);
}
function messageCallback(data) {
if (!suspended) {
ok(lastCount === undefined || lastCount == data - 1,
"Data is inconsistent");
lastCount = data;
if (lastCount == 50) {
setCachePref(true);
iframe.location = "about:blank";
suspended = true;
}
}
else {
var newLocation =
window.location.toString().replace("test_suspend.html",
"suspend_iframe.html");
is(iframe.location, newLocation, "Wrong url!");
ok(suspended && resumed, "Got message before resumed!");
is(lastCount, data - 1, "Missed a message, suspend failed!");
finishTest();
}
}
function errorCallback(data) {
ok(false, "Iframe had an error: '" + data + "'");
finishTest();
}
function subframeLoaded() {
var iframeElement = document.getElementById("workerFrame");
iframeElement.onload = suspendCallback;
iframe = iframeElement.contentWindow;
ok(iframe, "No iframe?!");
iframe.startWorker(messageCallback, errorCallback);
}
</script>
</pre>
</body>
</html>