gecko-dev/dom/workers/WorkerDebuggerManager.cpp

298 lines
7.7 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "WorkerDebuggerManager.h"
#include "nsISimpleEnumerator.h"
#include "WorkerPrivate.h"
USING_WORKERS_NAMESPACE
class RegisterDebuggerMainThreadRunnable final : public nsRunnable
{
RefPtr<WorkerDebuggerManager> mManager;
WorkerPrivate* mWorkerPrivate;
bool mNotifyListeners;
public:
RegisterDebuggerMainThreadRunnable(WorkerDebuggerManager* aManager,
WorkerPrivate* aWorkerPrivate,
bool aNotifyListeners)
: mManager(aManager),
mWorkerPrivate(aWorkerPrivate),
mNotifyListeners(aNotifyListeners)
{ }
private:
~RegisterDebuggerMainThreadRunnable()
{ }
NS_IMETHOD
Run() override
{
mManager->RegisterDebuggerMainThread(mWorkerPrivate, mNotifyListeners);
return NS_OK;
}
};
class UnregisterDebuggerMainThreadRunnable final : public nsRunnable
{
RefPtr<WorkerDebuggerManager> mManager;
WorkerPrivate* mWorkerPrivate;
public:
UnregisterDebuggerMainThreadRunnable(WorkerDebuggerManager* aManager,
WorkerPrivate* aWorkerPrivate)
: mManager(aManager), mWorkerPrivate(aWorkerPrivate)
{ }
private:
~UnregisterDebuggerMainThreadRunnable()
{ }
NS_IMETHOD
Run() override
{
mManager->UnregisterDebuggerMainThread(mWorkerPrivate);
return NS_OK;
}
};
BEGIN_WORKERS_NAMESPACE
class WorkerDebuggerEnumerator final : public nsISimpleEnumerator
{
nsTArray<RefPtr<WorkerDebugger>> mDebuggers;
uint32_t mIndex;
public:
explicit WorkerDebuggerEnumerator(
const nsTArray<RefPtr<WorkerDebugger>>& aDebuggers)
: mDebuggers(aDebuggers), mIndex(0)
{
}
NS_DECL_ISUPPORTS
NS_DECL_NSISIMPLEENUMERATOR
private:
~WorkerDebuggerEnumerator() {}
};
NS_IMPL_ISUPPORTS(WorkerDebuggerEnumerator, nsISimpleEnumerator);
NS_IMETHODIMP
WorkerDebuggerEnumerator::HasMoreElements(bool* aResult)
{
*aResult = mIndex < mDebuggers.Length();
return NS_OK;
};
NS_IMETHODIMP
WorkerDebuggerEnumerator::GetNext(nsISupports** aResult)
{
if (mIndex == mDebuggers.Length()) {
return NS_ERROR_FAILURE;
}
mDebuggers.ElementAt(mIndex++).forget(aResult);
return NS_OK;
};
WorkerDebuggerManager::WorkerDebuggerManager()
: mMutex("WorkerDebuggerManager::mMutex")
{
AssertIsOnMainThread();
}
WorkerDebuggerManager::~WorkerDebuggerManager()
{
AssertIsOnMainThread();
}
NS_IMPL_ISUPPORTS(WorkerDebuggerManager, nsIWorkerDebuggerManager);
NS_IMETHODIMP
WorkerDebuggerManager::GetWorkerDebuggerEnumerator(
nsISimpleEnumerator** aResult)
{
AssertIsOnMainThread();
RefPtr<WorkerDebuggerEnumerator> enumerator =
new WorkerDebuggerEnumerator(mDebuggers);
enumerator.forget(aResult);
return NS_OK;
}
NS_IMETHODIMP
WorkerDebuggerManager::AddListener(nsIWorkerDebuggerManagerListener* aListener)
{
AssertIsOnMainThread();
MutexAutoLock lock(mMutex);
if (mListeners.Contains(aListener)) {
return NS_ERROR_INVALID_ARG;
}
mListeners.AppendElement(aListener);
return NS_OK;
}
NS_IMETHODIMP
WorkerDebuggerManager::RemoveListener(
nsIWorkerDebuggerManagerListener* aListener)
{
AssertIsOnMainThread();
MutexAutoLock lock(mMutex);
if (!mListeners.Contains(aListener)) {
return NS_OK;
}
mListeners.RemoveElement(aListener);
return NS_OK;
}
void
WorkerDebuggerManager::ClearListeners()
{
AssertIsOnMainThread();
MutexAutoLock lock(mMutex);
mListeners.Clear();
}
void
WorkerDebuggerManager::RegisterDebugger(WorkerPrivate* aWorkerPrivate)
{
aWorkerPrivate->AssertIsOnParentThread();
if (NS_IsMainThread()) {
// When the parent thread is the main thread, it will always block until all
// register liseners have been called, since it cannot continue until the
// call to RegisterDebuggerMainThread returns.
//
// In this case, it is always safe to notify all listeners on the main
// thread, even if there were no listeners at the time this method was
// called, so we can always pass true for the value of aNotifyListeners.
// This avoids having to lock mMutex to check whether mListeners is empty.
RegisterDebuggerMainThread(aWorkerPrivate, true);
} else {
// We guarantee that if any register listeners are called, the worker does
// not start running until all register listeners have been called. To
// guarantee this, the parent thread should block until all register
// listeners have been called.
//
// However, to avoid overhead when the debugger is not being used, the
// parent thread will only block if there were any listeners at the time
// this method was called. As a result, we should not notify any listeners
// on the main thread if there were no listeners at the time this method was
// called, because the parent will not be blocking in that case.
bool hasListeners = false;
{
MutexAutoLock lock(mMutex);
hasListeners = !mListeners.IsEmpty();
}
nsCOMPtr<nsIRunnable> runnable =
new RegisterDebuggerMainThreadRunnable(this, aWorkerPrivate,
hasListeners);
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL)));
if (hasListeners) {
aWorkerPrivate->WaitForIsDebuggerRegistered(true);
}
}
}
void
WorkerDebuggerManager::UnregisterDebugger(WorkerPrivate* aWorkerPrivate)
{
aWorkerPrivate->AssertIsOnParentThread();
if (NS_IsMainThread()) {
UnregisterDebuggerMainThread(aWorkerPrivate);
} else {
nsCOMPtr<nsIRunnable> runnable =
new UnregisterDebuggerMainThreadRunnable(this, aWorkerPrivate);
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL)));
aWorkerPrivate->WaitForIsDebuggerRegistered(false);
}
}
void
WorkerDebuggerManager::RegisterDebuggerMainThread(WorkerPrivate* aWorkerPrivate,
bool aNotifyListeners)
{
AssertIsOnMainThread();
RefPtr<WorkerDebugger> debugger = new WorkerDebugger(aWorkerPrivate);
mDebuggers.AppendElement(debugger);
aWorkerPrivate->SetDebugger(debugger);
if (aNotifyListeners) {
nsTArray<nsCOMPtr<nsIWorkerDebuggerManagerListener>> listeners;
{
MutexAutoLock lock(mMutex);
listeners = mListeners;
}
for (size_t index = 0; index < listeners.Length(); ++index) {
listeners[index]->OnRegister(debugger);
}
}
aWorkerPrivate->SetIsDebuggerRegistered(true);
}
void
WorkerDebuggerManager::UnregisterDebuggerMainThread(
WorkerPrivate* aWorkerPrivate)
{
AssertIsOnMainThread();
// There is nothing to do here if the debugger was never succesfully
// registered. We need to check this on the main thread because the worker
// does not wait for the registration to complete if there were no listeners
// installed when it started.
if (!aWorkerPrivate->IsDebuggerRegistered()) {
return;
}
RefPtr<WorkerDebugger> debugger = aWorkerPrivate->Debugger();
mDebuggers.RemoveElement(debugger);
aWorkerPrivate->SetDebugger(nullptr);
nsTArray<nsCOMPtr<nsIWorkerDebuggerManagerListener>> listeners;
{
MutexAutoLock lock(mMutex);
listeners = mListeners;
}
for (size_t index = 0; index < listeners.Length(); ++index) {
listeners[index]->OnUnregister(debugger);
}
debugger->Close();
aWorkerPrivate->SetIsDebuggerRegistered(false);
}
END_WORKERS_NAMESPACE