summaryrefslogtreecommitdiffstats
path: root/dom/workers/WorkerDebuggerManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/workers/WorkerDebuggerManager.cpp')
-rw-r--r--dom/workers/WorkerDebuggerManager.cpp361
1 files changed, 361 insertions, 0 deletions
diff --git a/dom/workers/WorkerDebuggerManager.cpp b/dom/workers/WorkerDebuggerManager.cpp
new file mode 100644
index 000000000..dfd7e5acc
--- /dev/null
+++ b/dom/workers/WorkerDebuggerManager.cpp
@@ -0,0 +1,361 @@
+/* -*- 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 "mozilla/ClearOnShutdown.h"
+
+#include "WorkerPrivate.h"
+
+USING_WORKERS_NAMESPACE
+
+namespace {
+
+class RegisterDebuggerMainThreadRunnable final : public mozilla::Runnable
+{
+ WorkerPrivate* mWorkerPrivate;
+ bool mNotifyListeners;
+
+public:
+ RegisterDebuggerMainThreadRunnable(WorkerPrivate* aWorkerPrivate,
+ bool aNotifyListeners)
+ : mWorkerPrivate(aWorkerPrivate),
+ mNotifyListeners(aNotifyListeners)
+ { }
+
+private:
+ ~RegisterDebuggerMainThreadRunnable()
+ { }
+
+ NS_IMETHOD
+ Run() override
+ {
+ WorkerDebuggerManager* manager = WorkerDebuggerManager::Get();
+ MOZ_ASSERT(manager);
+
+ manager->RegisterDebuggerMainThread(mWorkerPrivate, mNotifyListeners);
+ return NS_OK;
+ }
+};
+
+class UnregisterDebuggerMainThreadRunnable final : public mozilla::Runnable
+{
+ WorkerPrivate* mWorkerPrivate;
+
+public:
+ explicit UnregisterDebuggerMainThreadRunnable(WorkerPrivate* aWorkerPrivate)
+ : mWorkerPrivate(aWorkerPrivate)
+ { }
+
+private:
+ ~UnregisterDebuggerMainThreadRunnable()
+ { }
+
+ NS_IMETHOD
+ Run() override
+ {
+ WorkerDebuggerManager* manager = WorkerDebuggerManager::Get();
+ MOZ_ASSERT(manager);
+
+ manager->UnregisterDebuggerMainThread(mWorkerPrivate);
+ return NS_OK;
+ }
+};
+
+// Does not hold an owning reference.
+static WorkerDebuggerManager* gWorkerDebuggerManager;
+
+} /* anonymous namespace */
+
+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();
+}
+
+// static
+already_AddRefed<WorkerDebuggerManager>
+WorkerDebuggerManager::GetInstance()
+{
+ RefPtr<WorkerDebuggerManager> manager = WorkerDebuggerManager::GetOrCreate();
+ return manager.forget();
+}
+
+// static
+WorkerDebuggerManager*
+WorkerDebuggerManager::GetOrCreate()
+{
+ AssertIsOnMainThread();
+
+ if (!gWorkerDebuggerManager) {
+ // The observer service now owns us until shutdown.
+ gWorkerDebuggerManager = new WorkerDebuggerManager();
+ if (NS_FAILED(gWorkerDebuggerManager->Init())) {
+ NS_WARNING("Failed to initialize worker debugger manager!");
+ gWorkerDebuggerManager = nullptr;
+ return nullptr;
+ }
+ }
+
+ return gWorkerDebuggerManager;
+}
+
+WorkerDebuggerManager*
+WorkerDebuggerManager::Get()
+{
+ MOZ_ASSERT(gWorkerDebuggerManager);
+ return gWorkerDebuggerManager;
+}
+
+NS_IMPL_ISUPPORTS(WorkerDebuggerManager, nsIObserver, nsIWorkerDebuggerManager);
+
+NS_IMETHODIMP
+WorkerDebuggerManager::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+{
+ if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ Shutdown();
+ return NS_OK;
+ }
+
+ NS_NOTREACHED("Unknown observer topic!");
+ return NS_OK;
+}
+
+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;
+}
+
+nsresult
+WorkerDebuggerManager::Init()
+{
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
+
+ nsresult rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+void
+WorkerDebuggerManager::Shutdown()
+{
+ 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(aWorkerPrivate, hasListeners);
+ MOZ_ALWAYS_SUCCEEDS(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(aWorkerPrivate);
+ MOZ_ALWAYS_SUCCEEDS(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