Bug 790919 - Don't dispatch close event in Workers, r=bkelly

This commit is contained in:
Andrea Marchesini 2016-08-31 21:33:05 -07:00
Родитель 0b570ff235
Коммит e8d47ac312
16 изменённых файлов: 30 добавлений и 467 удалений

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

@ -53,7 +53,6 @@ WorkerGlobalScope implements ImageBitmapFactories;
// Mozilla extensions
partial interface WorkerGlobalScope {
attribute EventHandler onclose;
void dump(optional DOMString str);

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

@ -215,18 +215,6 @@ public:
void
UpdateAllWorkerMemoryParameter(JSGCParamKey aKey, uint32_t aValue);
static uint32_t
GetContentCloseHandlerTimeoutSeconds()
{
return sDefaultJSSettings.content.maxScriptRuntime;
}
static uint32_t
GetChromeCloseHandlerTimeoutSeconds()
{
return sDefaultJSSettings.chrome.maxScriptRuntime;
}
#ifdef JS_GC_ZEAL
static void
SetDefaultGCZeal(uint8_t aGCZeal, uint32_t aFrequency)

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

@ -603,64 +603,6 @@ private:
}
};
class CloseEventRunnable final : public WorkerRunnable
{
public:
explicit CloseEventRunnable(WorkerPrivate* aWorkerPrivate)
: WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
{ }
private:
virtual bool
PreDispatch(WorkerPrivate* aWorkerPrivate) override
{
MOZ_CRASH("Don't call Dispatch() on CloseEventRunnable!");
}
virtual void
PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
{
MOZ_CRASH("Don't call Dispatch() on CloseEventRunnable!");
}
virtual bool
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
{
aWorkerPrivate->CloseHandlerStarted();
WorkerGlobalScope* globalScope = aWorkerPrivate->GlobalScope();
RefPtr<Event> event = NS_NewDOMEvent(globalScope, nullptr, nullptr);
event->InitEvent(NS_LITERAL_STRING("close"), false, false);
event->SetTrusted(true);
globalScope->DispatchDOMEvent(nullptr, event, nullptr, nullptr);
return true;
}
nsresult Cancel() override
{
// We need to run regardless.
Run();
return WorkerRunnable::Cancel();
}
virtual void
PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult)
override
{
// Report errors.
WorkerRunnable::PostRun(aCx, aWorkerPrivate, aRunResult);
// Match the busy count increase from NotifyRunnable.
aWorkerPrivate->ModifyBusyCountFromWorker(false);
aWorkerPrivate->CloseHandlerFinished();
}
};
class MessageEventRunnable final : public WorkerRunnable
, public StructuredCloneHolder
{
@ -899,8 +841,6 @@ private:
PreDispatch(WorkerPrivate* aWorkerPrivate) override
{
aWorkerPrivate->AssertIsOnParentThread();
// Modify here, but not in PostRun! This busy count addition will be matched
// by the CloseEventRunnable.
return aWorkerPrivate->ModifyBusyCount(true);
}
@ -915,6 +855,14 @@ private:
}
}
virtual void
PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult)
override
{
aWorkerPrivate->ModifyBusyCountFromWorker(false);
return;
}
virtual bool
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
{
@ -935,9 +883,7 @@ private:
virtual bool
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
{
// This busy count will be matched by the CloseEventRunnable.
return aWorkerPrivate->ModifyBusyCount(true) &&
aWorkerPrivate->Close();
return aWorkerPrivate->Close();
}
};
@ -1356,111 +1302,6 @@ DummyCallback(nsITimer* aTimer, void* aClosure)
// Nothing!
}
class KillCloseEventRunnable final : public WorkerRunnable
{
nsCOMPtr<nsITimer> mTimer;
class KillScriptRunnable final : public WorkerControlRunnable
{
public:
explicit KillScriptRunnable(WorkerPrivate* aWorkerPrivate)
: WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
{ }
private:
virtual bool
PreDispatch(WorkerPrivate* aWorkerPrivate) override
{
// Silence bad assertions, this is dispatched from the timer thread.
return true;
}
virtual void
PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
{
// Silence bad assertions, this is dispatched from the timer thread.
}
virtual bool
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
{
// Kill running script.
return false;
}
};
public:
explicit KillCloseEventRunnable(WorkerPrivate* aWorkerPrivate)
: WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
{ }
bool
SetTimeout(uint32_t aDelayMS)
{
nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
if (!timer) {
return false;
}
RefPtr<KillScriptRunnable> runnable =
new KillScriptRunnable(mWorkerPrivate);
RefPtr<TimerThreadEventTarget> target =
new TimerThreadEventTarget(mWorkerPrivate, runnable);
if (NS_FAILED(timer->SetTarget(target))) {
return false;
}
if (NS_FAILED(timer->InitWithNamedFuncCallback(
DummyCallback, nullptr, aDelayMS, nsITimer::TYPE_ONE_SHOT,
"dom::workers::DummyCallback(1)"))) {
return false;
}
mTimer.swap(timer);
return true;
}
private:
~KillCloseEventRunnable()
{
if (mTimer) {
mTimer->Cancel();
}
}
virtual bool
PreDispatch(WorkerPrivate* aWorkerPrivate) override
{
MOZ_CRASH("Don't call Dispatch() on KillCloseEventRunnable!");
}
virtual void
PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
{
MOZ_CRASH("Don't call Dispatch() on KillCloseEventRunnable!");
}
virtual bool
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
{
if (mTimer) {
mTimer->Cancel();
mTimer = nullptr;
}
return true;
}
nsresult Cancel() override
{
// We need to run regardless.
Run();
return WorkerRunnable::Cancel();
}
};
class UpdateContextOptionsRunnable final : public WorkerControlRunnable
{
JS::ContextOptions mContextOptions;
@ -4035,8 +3876,6 @@ WorkerPrivate::WorkerPrivate(WorkerPrivate* aParent,
, mFrozen(false)
, mTimerRunning(false)
, mRunningExpiredTimeouts(false)
, mCloseHandlerStarted(false)
, mCloseHandlerFinished(false)
, mPendingEventQueueClearing(false)
, mMemoryReporterRunning(false)
, mBlockedForMemoryReporter(false)
@ -4258,7 +4097,6 @@ WorkerPrivate::GetLoadInfo(JSContext* aCx, nsPIDOMWindowInner* aWindow,
}
if (parentStatus > Running) {
NS_WARNING("Cannot create child workers from the close handler!");
return NS_ERROR_FAILURE;
}
@ -4536,12 +4374,13 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
Maybe<JSAutoCompartment> workerCompartment;
for (;;) {
Status currentStatus;
Status currentStatus, previousStatus;
bool debuggerRunnablesPending = false;
bool normalRunnablesPending = false;
{
MutexAutoLock lock(mMutex);
previousStatus = mStatus;
while (mControlQueue.IsEmpty() &&
!(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty()) &&
@ -4562,10 +4401,11 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
currentStatus = mStatus;
}
// If the close handler has finished and all holders are done then we can
// kill this thread.
// if all holders are done then we can kill this thread.
if (currentStatus != Running && !HasActiveHolders()) {
if (mCloseHandlerFinished && currentStatus != Killing) {
// If we just changed status, we must schedule the current runnables.
if (previousStatus != Running && currentStatus != Killing) {
NotifyInternal(aCx, Killing);
MOZ_ASSERT(!JS_IsExceptionPending(aCx));
@ -4646,10 +4486,8 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
JS_MaybeGC(aCx);
}
} else if (normalRunnablesPending) {
MOZ_ASSERT(NS_HasPendingEvents(mThread));
// Process a single runnable from the main queue.
MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(mThread, false));
NS_ProcessNextEvent(mThread, false);
normalRunnablesPending = NS_HasPendingEvents(mThread);
if (normalRunnablesPending && GlobalScope()) {
@ -4851,7 +4689,7 @@ WorkerPrivate::InterruptCallback(JSContext* aCx)
break;
}
WaitForWorkerEvents(PR_MillisecondsToInterval(RemainingRunTimeMS()));
WaitForWorkerEvents(PR_MillisecondsToInterval(UINT32_MAX));
}
}
@ -5153,17 +4991,6 @@ WorkerPrivate::ClearDebuggerEventQueue()
}
}
uint32_t
WorkerPrivate::RemainingRunTimeMS() const
{
if (mKillTime.IsNull()) {
return UINT32_MAX;
}
TimeDuration runtime = mKillTime - TimeStamp::Now();
double ms = runtime > TimeDuration(0) ? runtime.ToMilliseconds() : 0;
return ms > double(UINT32_MAX) ? UINT32_MAX : uint32_t(ms);
}
bool
WorkerPrivate::FreezeInternal()
{
@ -5802,8 +5629,6 @@ WorkerPrivate::NotifyInternal(JSContext* aCx, Status aStatus)
MOZ_ASSERT(previousStatus != Pending);
MOZ_ASSERT(previousStatus >= Canceling || mKillTime.IsNull());
// Let all our holders know the new status.
NotifyHolders(aCx, aStatus);
MOZ_ASSERT(!JS_IsExceptionPending(aCx));
@ -5820,28 +5645,12 @@ WorkerPrivate::NotifyInternal(JSContext* aCx, Status aStatus)
}
}
// If we've run the close handler, we don't need to do anything else.
if (mCloseHandlerFinished) {
return true;
}
// If the worker script never ran, or failed to compile, we don't need to do
// anything else, except pretend that we ran the close handler.
// anything else.
if (!GlobalScope()) {
mCloseHandlerStarted = true;
mCloseHandlerFinished = true;
return true;
}
// If this is the first time our status has changed we also need to schedule
// the close handler unless we're being shut down.
if (previousStatus == Running && aStatus != Killing) {
MOZ_ASSERT(!mCloseHandlerStarted && !mCloseHandlerFinished);
RefPtr<CloseEventRunnable> closeRunnable = new CloseEventRunnable(this);
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(closeRunnable));
}
if (aStatus == Closing) {
// Notify parent to stop sending us messages and balance our busy count.
RefPtr<CloseRunnable> runnable = new CloseRunnable(this);
@ -5853,60 +5662,14 @@ WorkerPrivate::NotifyInternal(JSContext* aCx, Status aStatus)
return true;
}
if (aStatus == Terminating) {
// Only abort the script if we're not yet running the close handler.
return mCloseHandlerStarted;
}
if (aStatus == Canceling) {
// We need to enforce a timeout on the close handler.
MOZ_ASSERT(previousStatus >= Running && previousStatus <= Terminating);
uint32_t killSeconds = IsChromeWorker() ?
RuntimeService::GetChromeCloseHandlerTimeoutSeconds() :
RuntimeService::GetContentCloseHandlerTimeoutSeconds();
if (killSeconds) {
mKillTime = TimeStamp::Now() + TimeDuration::FromSeconds(killSeconds);
if (!mCloseHandlerFinished && !ScheduleKillCloseEventRunnable()) {
return false;
}
}
// Only abort the script if we're not yet running the close handler.
return mCloseHandlerStarted;
}
MOZ_ASSERT(aStatus == Killing);
mKillTime = TimeStamp::Now();
if (mCloseHandlerStarted && !mCloseHandlerFinished) {
ScheduleKillCloseEventRunnable();
}
MOZ_ASSERT(aStatus == Terminating ||
aStatus == Canceling ||
aStatus == Killing);
// Always abort the script.
return false;
}
bool
WorkerPrivate::ScheduleKillCloseEventRunnable()
{
AssertIsOnWorkerThread();
MOZ_ASSERT(!mKillTime.IsNull());
RefPtr<KillCloseEventRunnable> killCloseEventRunnable =
new KillCloseEventRunnable(this);
if (!killCloseEventRunnable->SetTimeout(RemainingRunTimeMS())) {
return false;
}
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(killCloseEventRunnable));
return true;
}
void
WorkerPrivate::ReportError(JSContext* aCx, const char* aFallbackMessage,
JSErrorReport* aReport)
@ -5965,9 +5728,8 @@ WorkerPrivate::ReportError(JSContext* aCx, const char* aFallbackMessage,
mErrorHandlerRecursionCount++;
// Don't want to run the scope's error handler if this is a recursive error or
// if there was an error in the close handler or if we ran out of memory.
// if we ran out of memory.
bool fireAtScope = mErrorHandlerRecursionCount == 1 &&
!mCloseHandlerStarted &&
errorNumber != JSMSG_OUT_OF_MEMORY &&
JS::CurrentGlobalOrNull(aCx);
@ -6008,13 +5770,6 @@ WorkerPrivate::SetTimeout(JSContext* aCx,
currentStatus = mStatus;
}
// It's a script bug if setTimeout/setInterval are called from a close handler
// so throw an exception.
if (currentStatus == Closing) {
aRv.Throw(NS_ERROR_FAILURE);
return 0;
}
// If the worker is trying to call setTimeout/setInterval and the parent
// thread has initiated the close process then just silently fail.
if (currentStatus >= Closing) {

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

@ -950,8 +950,6 @@ class WorkerPrivate : public WorkerPrivateParent<WorkerPrivate>
bool mFrozen;
bool mTimerRunning;
bool mRunningExpiredTimeouts;
bool mCloseHandlerStarted;
bool mCloseHandlerFinished;
bool mPendingEventQueueClearing;
bool mMemoryReporterRunning;
bool mBlockedForMemoryReporter;
@ -1155,20 +1153,6 @@ public:
bool
RescheduleTimeoutTimer(JSContext* aCx);
void
CloseHandlerStarted()
{
AssertIsOnWorkerThread();
mCloseHandlerStarted = true;
}
void
CloseHandlerFinished()
{
AssertIsOnWorkerThread();
mCloseHandlerFinished = true;
}
void
UpdateContextOptionsInternal(JSContext* aCx, const JS::ContextOptions& aContextOptions);
@ -1369,24 +1353,16 @@ private:
status = mStatus;
}
if (status >= Killing) {
return false;
if (status < Terminating) {
return true;
}
if (status >= Running) {
return mKillTime.IsNull() || RemainingRunTimeMS() > 0;
}
return true;
}
uint32_t
RemainingRunTimeMS() const;
return false;
}
void
CancelAllTimeouts();
bool
ScheduleKillCloseEventRunnable();
enum class ProcessAllControlRunnablesResult
{
// We did not process anything.

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

@ -148,7 +148,6 @@ public:
IMPL_EVENT_HANDLER(online)
IMPL_EVENT_HANDLER(offline)
IMPL_EVENT_HANDLER(close)
void
Dump(const Optional<nsAString>& aString) const;

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

@ -1,23 +0,0 @@
/**
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
function handleRequest(request, response)
{
response.setHeader("Content-Type", "text/plain", false);
response.setHeader("Cache-Control", "no-cache", false);
if (request.method == "POST") {
setState("seenPost" + request.queryString, "1");
return;
}
if (request.method == "GET") {
if (getState("seenPost" + request.queryString) == "1") {
response.write("closed");
}
return;
}
response.setStatusLine(request.httpVersion, 404, "Not found");
}

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

@ -1,11 +0,0 @@
/**
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
onclose = function() {
var xhr = new XMLHttpRequest();
xhr.open("POST", "closeOnGC_server.sjs" + location.search, false);
xhr.send();
};
postMessage("ready");

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

@ -1,21 +0,0 @@
/**
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
onclose = function() {
postMessage("closed");
// Try to open a new worker.
try {
var worker = new Worker("close_worker.js");
throw new Error("We shouldn't get here!");
} catch (e) {
// pass
}
};
setTimeout(function () {
setTimeout(function () {
throw new Error("I should never run!");
}, 1000);
close();
}, 1000);

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

@ -11,9 +11,6 @@ support-files =
bug998474_worker.js
bug1063538_worker.js
clearTimeouts_worker.js
closeOnGC_server.sjs
closeOnGC_worker.js
close_worker.js
content_worker.js
console_worker.js
consoleReplaceable_worker.js
@ -146,8 +143,6 @@ skip-if = true # bug 1176225
[test_bug1132924.html]
[test_chromeWorker.html]
[test_clearTimeouts.html]
[test_close.html]
[test_closeOnGC.html]
[test_console.html]
[test_consoleAndBlobs.html]
[test_consoleReplaceable.html]

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

@ -2,10 +2,6 @@
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
onclose = function() {
postMessage("Closed!");
}
onmessage = function(event) {
throw "No messages should reach me!";
}

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

@ -1,29 +0,0 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<!DOCTYPE HTML>
<html>
<head>
<title>Test for DOM Worker Threads</title>
<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">
<script class="testbody" type="text/javascript">
var worker = new Worker("close_worker.js");
worker.onmessage = function(event) {
is(event.data, "closed");
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
</script>
</pre>
</body>
</html>

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

@ -1,57 +0,0 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<!DOCTYPE HTML>
<html>
<head>
<title>Test for DOM Worker Threads</title>
<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">
<script class="testbody" type="text/javascript">
var count = 0;
function testWorker(queryString) {
++count;
var worker = new Worker("closeOnGC_worker.js?" + queryString);
worker.onmessage = function(event) {
is(event.data, "ready");
worker = null;
}
var interval = setInterval(function() {
var xhr = new XMLHttpRequest();
xhr.open("GET", "closeOnGC_server.sjs?" + queryString, false);
xhr.send();
if (xhr.responseText != "closed") {
SpecialPowers.gc();
return;
}
clearInterval(interval);
ok(true, "xhr correctly closed");
if (--count == 0) {
SimpleTest.finish();
}
}, 500);
return worker;
}
testWorker("white");
var worker = testWorker("gray");
worker.onerror = function() {};
worker.onerror.foo = worker;
worker = null;
SimpleTest.waitForExplicitFinish();
</script>
</pre>
</body>
</html>

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

@ -30,7 +30,7 @@ Tests of DOM Worker terminate feature
function messageListener(event) {
is(event.data, "Still alive!", "Correct message!");
if (messageCount++ == 20) {
if (++messageCount == 20) {
ok(worker.onmessage === messageListener,
"Correct listener before terminate");
@ -82,7 +82,7 @@ Tests of DOM Worker terminate feature
}
function testCount() {
is(messageCount, 21, "Received another message after terminated!");
is(messageCount, 20, "Received another message after terminated!");
if (intervalCount++ == 5) {
clearInterval(interval);
SimpleTest.finish();

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

@ -227,9 +227,3 @@ onmessage = event => {
});
});
};
onclose = event => {
io.shutdown();
self.postMessage({msg: "close"});
};

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

@ -500,6 +500,7 @@ io = {
this.signal.cleanup();
this.signal = null;
self.postMessage({msg: "close"});
self.close();
}
},

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

@ -599,6 +599,7 @@ io = {
this.signal.cleanup();
this.signal = null;
self.postMessage({msg: "close"});
self.close();
}
},