Bug 1178721 - Implement SuspendWorkersForWindow;r=khuey

This commit is contained in:
Eddy Bruel 2015-10-07 12:20:59 +02:00
parent 012753246e
commit ec8b2add25
13 changed files with 246 additions and 21 deletions

View File

@ -2081,7 +2081,7 @@ nsDOMWindowUtils::SuspendTimeouts()
nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow); nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
window->SuspendTimeouts(); window->SuspendTimeouts(1, true, false);
return NS_OK; return NS_OK;
} }
@ -2092,7 +2092,7 @@ nsDOMWindowUtils::ResumeTimeouts()
nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow); nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
window->ResumeTimeouts(); window->ResumeTimeouts(true, false);
return NS_OK; return NS_OK;
} }

View File

@ -13376,9 +13376,11 @@ nsGlobalWindow::RestoreWindowState(nsISupports *aState)
void void
nsGlobalWindow::SuspendTimeouts(uint32_t aIncrease, nsGlobalWindow::SuspendTimeouts(uint32_t aIncrease,
bool aFreezeChildren) bool aFreezeChildren,
bool aFreezeWorkers)
{ {
FORWARD_TO_INNER_VOID(SuspendTimeouts, (aIncrease, aFreezeChildren)); FORWARD_TO_INNER_VOID(SuspendTimeouts,
(aIncrease, aFreezeChildren, aFreezeWorkers));
bool suspended = (mTimeoutsSuspendDepth != 0); bool suspended = (mTimeoutsSuspendDepth != 0);
mTimeoutsSuspendDepth += aIncrease; mTimeoutsSuspendDepth += aIncrease;
@ -13391,8 +13393,12 @@ nsGlobalWindow::SuspendTimeouts(uint32_t aIncrease,
} }
DisableGamepadUpdates(); DisableGamepadUpdates();
// Freeze all of the workers for this window. // Freeze or suspend all of the workers for this window.
mozilla::dom::workers::FreezeWorkersForWindow(this); if (aFreezeWorkers) {
mozilla::dom::workers::FreezeWorkersForWindow(this);
} else {
mozilla::dom::workers::SuspendWorkersForWindow(this);
}
TimeStamp now = TimeStamp::Now(); TimeStamp now = TimeStamp::Now();
for (nsTimeout *t = mTimeouts.getFirst(); t; t = t->getNext()) { for (nsTimeout *t = mTimeouts.getFirst(); t; t = t->getNext()) {
@ -13448,7 +13454,7 @@ nsGlobalWindow::SuspendTimeouts(uint32_t aIncrease,
continue; continue;
} }
win->SuspendTimeouts(aIncrease, aFreezeChildren); win->SuspendTimeouts(aIncrease, aFreezeChildren, aFreezeWorkers);
if (inner && aFreezeChildren) { if (inner && aFreezeChildren) {
inner->Freeze(); inner->Freeze();
@ -13459,9 +13465,10 @@ nsGlobalWindow::SuspendTimeouts(uint32_t aIncrease,
} }
nsresult nsresult
nsGlobalWindow::ResumeTimeouts(bool aThawChildren) nsGlobalWindow::ResumeTimeouts(bool aThawChildren, bool aThawWorkers)
{ {
FORWARD_TO_INNER(ResumeTimeouts, (), NS_ERROR_NOT_INITIALIZED); FORWARD_TO_INNER(ResumeTimeouts, (aThawChildren, aThawWorkers),
NS_ERROR_NOT_INITIALIZED);
NS_ASSERTION(mTimeoutsSuspendDepth, "Mismatched calls to ResumeTimeouts!"); NS_ASSERTION(mTimeoutsSuspendDepth, "Mismatched calls to ResumeTimeouts!");
--mTimeoutsSuspendDepth; --mTimeoutsSuspendDepth;
@ -13482,8 +13489,12 @@ nsGlobalWindow::ResumeTimeouts(bool aThawChildren)
nsRefPtr<Promise> d = mAudioContexts[i]->Resume(dummy); nsRefPtr<Promise> d = mAudioContexts[i]->Resume(dummy);
} }
// Thaw all of the workers for this window. // Thaw or resume all of the workers for this window.
mozilla::dom::workers::ThawWorkersForWindow(this); if (aThawWorkers) {
mozilla::dom::workers::ThawWorkersForWindow(this);
} else {
mozilla::dom::workers::ResumeWorkersForWindow(this);
}
// Restore all of the timeouts, using the stored time remaining // Restore all of the timeouts, using the stored time remaining
// (stored in timeout->mTimeRemaining). // (stored in timeout->mTimeRemaining).
@ -13562,7 +13573,7 @@ nsGlobalWindow::ResumeTimeouts(bool aThawChildren)
inner->Thaw(); inner->Thaw();
} }
rv = win->ResumeTimeouts(aThawChildren); rv = win->ResumeTimeouts(aThawChildren, aThawWorkers);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
} }
} }

View File

@ -445,8 +445,10 @@ public:
virtual already_AddRefed<nsISupports> SaveWindowState() override; virtual already_AddRefed<nsISupports> SaveWindowState() override;
virtual nsresult RestoreWindowState(nsISupports *aState) override; virtual nsresult RestoreWindowState(nsISupports *aState) override;
virtual void SuspendTimeouts(uint32_t aIncrease = 1, virtual void SuspendTimeouts(uint32_t aIncrease = 1,
bool aFreezeChildren = true) override; bool aFreezeChildren = true,
virtual nsresult ResumeTimeouts(bool aThawChildren = true) override; bool aFreezeWorkers = true) override;
virtual nsresult ResumeTimeouts(bool aThawChildren = true,
bool aThawWorkers = true) override;
virtual uint32_t TimeoutSuspendCount() override; virtual uint32_t TimeoutSuspendCount() override;
virtual nsresult FireDelayedDOMEvents() override; virtual nsresult FireDelayedDOMEvents() override;
virtual bool IsFrozen() const override virtual bool IsFrozen() const override

View File

@ -291,10 +291,12 @@ public:
// Suspend timeouts in this window and in child windows. // Suspend timeouts in this window and in child windows.
virtual void SuspendTimeouts(uint32_t aIncrease = 1, virtual void SuspendTimeouts(uint32_t aIncrease = 1,
bool aFreezeChildren = true) = 0; bool aFreezeChildren = true,
bool aFreezeWorkers = true) = 0;
// Resume suspended timeouts in this window and in child windows. // Resume suspended timeouts in this window and in child windows.
virtual nsresult ResumeTimeouts(bool aThawChildren = true) = 0; virtual nsresult ResumeTimeouts(bool aThawChildren = true,
bool aThawWorkers = true) = 0;
virtual uint32_t TimeoutSuspendCount() = 0; virtual uint32_t TimeoutSuspendCount() = 0;

View File

@ -1284,6 +1284,26 @@ ThawWorkersForWindow(nsPIDOMWindow* aWindow)
} }
} }
void
SuspendWorkersForWindow(nsPIDOMWindow* aWindow)
{
AssertIsOnMainThread();
RuntimeService* runtime = RuntimeService::GetService();
if (runtime) {
runtime->SuspendWorkersForWindow(aWindow);
}
}
void
ResumeWorkersForWindow(nsPIDOMWindow* aWindow)
{
AssertIsOnMainThread();
RuntimeService* runtime = RuntimeService::GetService();
if (runtime) {
runtime->ResumeWorkersForWindow(aWindow);
}
}
WorkerCrossThreadDispatcher::WorkerCrossThreadDispatcher( WorkerCrossThreadDispatcher::WorkerCrossThreadDispatcher(
WorkerPrivate* aWorkerPrivate) WorkerPrivate* aWorkerPrivate)
: mMutex("WorkerCrossThreadDispatcher::mMutex"), : mMutex("WorkerCrossThreadDispatcher::mMutex"),
@ -2404,6 +2424,34 @@ RuntimeService::ThawWorkersForWindow(nsPIDOMWindow* aWindow)
} }
} }
void
RuntimeService::SuspendWorkersForWindow(nsPIDOMWindow* aWindow)
{
AssertIsOnMainThread();
MOZ_ASSERT(aWindow);
nsAutoTArray<WorkerPrivate*, MAX_WORKERS_PER_DOMAIN> workers;
GetWorkersForWindow(aWindow, workers);
for (uint32_t index = 0; index < workers.Length(); index++) {
workers[index]->Suspend();
}
}
void
RuntimeService::ResumeWorkersForWindow(nsPIDOMWindow* aWindow)
{
AssertIsOnMainThread();
MOZ_ASSERT(aWindow);
nsAutoTArray<WorkerPrivate*, MAX_WORKERS_PER_DOMAIN> workers;
GetWorkersForWindow(aWindow, workers);
for (uint32_t index = 0; index < workers.Length(); index++) {
workers[index]->Resume();
}
}
nsresult nsresult
RuntimeService::CreateSharedWorker(const GlobalObject& aGlobal, RuntimeService::CreateSharedWorker(const GlobalObject& aGlobal,
const nsAString& aScriptURL, const nsAString& aScriptURL,

View File

@ -148,6 +148,12 @@ public:
void void
ThawWorkersForWindow(nsPIDOMWindow* aWindow); ThawWorkersForWindow(nsPIDOMWindow* aWindow);
void
SuspendWorkersForWindow(nsPIDOMWindow* aWindow);
void
ResumeWorkersForWindow(nsPIDOMWindow* aWindow);
nsresult nsresult
CreateSharedWorker(const GlobalObject& aGlobal, CreateSharedWorker(const GlobalObject& aGlobal,
const nsAString& aScriptURL, const nsAString& aScriptURL,

View File

@ -678,7 +678,8 @@ private:
return true; return true;
} }
if (aWorkerPrivate->IsFrozen()) { if (aWorkerPrivate->IsFrozen() || aWorkerPrivate->IsSuspended()) {
MOZ_ASSERT(!IsDebuggerRunnable());
aWorkerPrivate->QueueRunnable(this); aWorkerPrivate->QueueRunnable(this);
return true; return true;
} }
@ -1006,7 +1007,8 @@ private:
else { else {
AssertIsOnMainThread(); AssertIsOnMainThread();
if (aWorkerPrivate->IsFrozen()) { if (aWorkerPrivate->IsFrozen() || aWorkerPrivate->IsSuspended()) {
MOZ_ASSERT(!IsDebuggerRunnable());
aWorkerPrivate->QueueRunnable(this); aWorkerPrivate->QueueRunnable(this);
return true; return true;
} }
@ -2108,8 +2110,8 @@ WorkerPrivateParent<Derived>::WorkerPrivateParent(
mParent(aParent), mScriptURL(aScriptURL), mParent(aParent), mScriptURL(aScriptURL),
mWorkerName(aWorkerName), mLoadingWorkerScript(false), mWorkerName(aWorkerName), mLoadingWorkerScript(false),
mBusyCount(0), mParentStatus(Pending), mParentFrozen(false), mBusyCount(0), mParentStatus(Pending), mParentFrozen(false),
mIsChromeWorker(aIsChromeWorker), mMainThreadObjectsForgotten(false), mParentSuspended(false), mIsChromeWorker(aIsChromeWorker),
mWorkerType(aWorkerType), mMainThreadObjectsForgotten(false), mWorkerType(aWorkerType),
mCreationTimeStamp(TimeStamp::Now()), mCreationTimeStamp(TimeStamp::Now()),
mCreationTimeHighRes((double)PR_Now() / PR_USEC_PER_MSEC) mCreationTimeHighRes((double)PR_Now() / PR_USEC_PER_MSEC)
{ {
@ -2576,7 +2578,7 @@ WorkerPrivateParent<Derived>::Thaw(JSContext* aCx, nsPIDOMWindow* aWindow)
// Execute queued runnables before waking up the worker, otherwise the worker // Execute queued runnables before waking up the worker, otherwise the worker
// could post new messages before we run those that have been queued. // could post new messages before we run those that have been queued.
if (!mQueuedRunnables.IsEmpty()) { if (!IsSuspended() && !mQueuedRunnables.IsEmpty()) {
AssertIsOnMainThread(); AssertIsOnMainThread();
MOZ_ASSERT(IsDedicatedWorker()); MOZ_ASSERT(IsDedicatedWorker());
@ -2597,6 +2599,50 @@ WorkerPrivateParent<Derived>::Thaw(JSContext* aCx, nsPIDOMWindow* aWindow)
return true; return true;
} }
template <class Derived>
void
WorkerPrivateParent<Derived>::Suspend()
{
AssertIsOnMainThread();
MOZ_ASSERT(!mParentSuspended, "Suspended more than once!");
mParentSuspended = true;
}
template <class Derived>
void
WorkerPrivateParent<Derived>::Resume()
{
AssertIsOnMainThread();
MOZ_ASSERT(mParentSuspended, "Resumed more than once!");
mParentSuspended = false;
{
MutexAutoLock lock(mMutex);
if (mParentStatus >= Terminating) {
return;
}
}
// Execute queued runnables before waking up, otherwise the worker could post
// new messages before we run those that have been queued.
if (!IsFrozen() && !mQueuedRunnables.IsEmpty()) {
AssertIsOnMainThread();
MOZ_ASSERT(IsDedicatedWorker());
nsTArray<nsCOMPtr<nsIRunnable>> runnables;
mQueuedRunnables.SwapElements(runnables);
for (uint32_t index = 0; index < runnables.Length(); index++) {
runnables[index]->Run();
}
}
}
template <class Derived> template <class Derived>
bool bool
WorkerPrivateParent<Derived>::Close() WorkerPrivateParent<Derived>::Close()

View File

@ -185,6 +185,7 @@ private:
uint64_t mBusyCount; uint64_t mBusyCount;
Status mParentStatus; Status mParentStatus;
bool mParentFrozen; bool mParentFrozen;
bool mParentSuspended;
bool mIsChromeWorker; bool mIsChromeWorker;
bool mMainThreadObjectsForgotten; bool mMainThreadObjectsForgotten;
WorkerType mWorkerType; WorkerType mWorkerType;
@ -305,6 +306,12 @@ public:
bool bool
Thaw(JSContext* aCx, nsPIDOMWindow* aWindow); Thaw(JSContext* aCx, nsPIDOMWindow* aWindow);
void
Suspend();
void
Resume();
bool bool
Terminate(JSContext* aCx) Terminate(JSContext* aCx)
{ {
@ -399,6 +406,13 @@ public:
return mParentFrozen; return mParentFrozen;
} }
bool
IsSuspended() const
{
AssertIsOnParentThread();
return mParentSuspended;
}
bool bool
IsAcceptingEvents() IsAcceptingEvents()
{ {

View File

@ -295,6 +295,12 @@ FreezeWorkersForWindow(nsPIDOMWindow* aWindow);
void void
ThawWorkersForWindow(nsPIDOMWindow* aWindow); ThawWorkersForWindow(nsPIDOMWindow* aWindow);
void
SuspendWorkersForWindow(nsPIDOMWindow* aWindow);
void
ResumeWorkersForWindow(nsPIDOMWindow* aWindow);
class WorkerTask class WorkerTask
{ {
protected: protected:

View File

@ -0,0 +1,6 @@
"use strict";
var dbg = new Debugger(global);
dbg.onDebuggerStatement = function (frame) {
postMessage("debugger");
};

View File

@ -0,0 +1,6 @@
"use strict";
self.onmessage = function () {
postMessage("worker");
debugger;
};

View File

@ -27,6 +27,8 @@ support-files =
WorkerDebugger_childWorker.js WorkerDebugger_childWorker.js
WorkerDebugger_worker.js WorkerDebugger_worker.js
WorkerDebugger_sharedWorker.js WorkerDebugger_sharedWorker.js
WorkerDebugger_suspended_debugger.js
WorkerDebugger_suspended_worker.js
WorkerTest.jsm WorkerTest.jsm
WorkerTest_subworker.js WorkerTest_subworker.js
WorkerTest_worker.js WorkerTest_worker.js
@ -61,6 +63,7 @@ support-files =
[test_WorkerDebuggerGlobalScope.reportError.xul] [test_WorkerDebuggerGlobalScope.reportError.xul]
[test_WorkerDebuggerGlobalScope.setImmediate.xul] [test_WorkerDebuggerGlobalScope.setImmediate.xul]
[test_WorkerDebuggerManager.xul] [test_WorkerDebuggerManager.xul]
[test_WorkerDebugger_suspended.xul]
[test_bug883784.xul] [test_bug883784.xul]
[test_chromeWorker.xul] [test_chromeWorker.xul]
[test_chromeWorkerJSM.xul] [test_chromeWorkerJSM.xul]

View File

@ -0,0 +1,75 @@
<?xml version="1.0"?>
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<window title="Test for WorkerDebugger with suspended workers"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="test();">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
<script type="application/javascript" src="dom_worker_helper.js"/>
<script type="application/javascript">
<![CDATA[
const WORKER_URL = "WorkerDebugger_suspended_worker.js";
const DEBUGGER_URL = BASE_URL + "WorkerDebugger_suspended_debugger.js";
function test() {
Task.spawn(function* () {
SimpleTest.waitForExplicitFinish();
info("Create a worker, wait for its debugger to be registered, and " +
"initialize it.");
let promise = waitForRegister(WORKER_URL, DEBUGGER_URL);
let worker = new Worker(WORKER_URL);
let dbg = yield promise;
info("Send a request to the worker. This should cause both the " +
"worker and the worker debugger to send a response.");
promise = waitForMultiple([
waitForWorkerMessage(worker, "worker"),
waitForDebuggerMessage(dbg, "debugger")
]);
worker.postMessage("ping");
yield promise;
info("Suspend the workers for this window, and send another request " +
"to the worker. This should cause only the worker debugger to " +
"send a response.");
let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
windowUtils.suspendTimeouts();
function onmessage() {
ok(false, "The worker should not send a response.");
};
worker.addEventListener("message", onmessage);
promise = waitForDebuggerMessage(dbg, "debugger");
worker.postMessage("ping");
yield promise;
worker.removeEventListener("message", onmessage);
info("Resume the workers for this window. This should cause the " +
"worker to send a response to the previous request.");
promise = waitForWorkerMessage(worker, "worker");
windowUtils.resumeTimeouts();
yield promise;
SimpleTest.finish();
});
}
]]>
</script>
<body xmlns="http://www.w3.org/1999/xhtml">
<p id="display"></p>
<div id="content" style="display:none;"></div>
<pre id="test"></pre>
</body>
<label id="test-result"/>
</window>