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.cpp330
1 files changed, 330 insertions, 0 deletions
diff --git a/dom/workers/WorkerDebuggerManager.cpp b/dom/workers/WorkerDebuggerManager.cpp
new file mode 100644
index 0000000000..dfca4748b7
--- /dev/null
+++ b/dom/workers/WorkerDebuggerManager.cpp
@@ -0,0 +1,330 @@
+/* -*- 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 "nsSimpleEnumerator.h"
+
+#include "mozilla/dom/JSExecutionManager.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+
+#include "WorkerDebugger.h"
+#include "WorkerPrivate.h"
+#include "nsIObserverService.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+class RegisterDebuggerMainThreadRunnable final : public mozilla::Runnable {
+ WorkerPrivate* mWorkerPrivate;
+ bool mNotifyListeners;
+
+ public:
+ RegisterDebuggerMainThreadRunnable(WorkerPrivate* aWorkerPrivate,
+ bool aNotifyListeners)
+ : mozilla::Runnable("RegisterDebuggerMainThreadRunnable"),
+ mWorkerPrivate(aWorkerPrivate),
+ mNotifyListeners(aNotifyListeners) {}
+
+ private:
+ ~RegisterDebuggerMainThreadRunnable() = default;
+
+ 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)
+ : mozilla::Runnable("UnregisterDebuggerMainThreadRunnable"),
+ mWorkerPrivate(aWorkerPrivate) {}
+
+ private:
+ ~UnregisterDebuggerMainThreadRunnable() = default;
+
+ NS_IMETHOD
+ Run() override {
+ WorkerDebuggerManager* manager = WorkerDebuggerManager::Get();
+ MOZ_ASSERT(manager);
+
+ manager->UnregisterDebuggerMainThread(mWorkerPrivate);
+ return NS_OK;
+ }
+};
+
+static StaticRefPtr<WorkerDebuggerManager> gWorkerDebuggerManager;
+
+} /* anonymous namespace */
+
+class WorkerDebuggerEnumerator final : public nsSimpleEnumerator {
+ nsTArray<RefPtr<WorkerDebugger>> mDebuggers;
+ uint32_t mIndex;
+
+ public:
+ explicit WorkerDebuggerEnumerator(
+ const nsTArray<RefPtr<WorkerDebugger>>& aDebuggers)
+ : mDebuggers(aDebuggers.Clone()), mIndex(0) {}
+
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ const nsID& DefaultInterface() override {
+ return NS_GET_IID(nsIWorkerDebugger);
+ }
+
+ private:
+ ~WorkerDebuggerEnumerator() override = default;
+};
+
+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_SUCCEEDED(gWorkerDebuggerManager->Init())) {
+ ClearOnShutdown(&gWorkerDebuggerManager);
+ } else {
+ NS_WARNING("Failed to initialize worker debugger manager!");
+ gWorkerDebuggerManager = 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;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("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) {
+ for (const auto& listener : CloneListeners()) {
+ listener->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);
+
+ for (const auto& listener : CloneListeners()) {
+ listener->OnUnregister(debugger);
+ }
+
+ debugger->Close();
+ aWorkerPrivate->SetIsDebuggerRegistered(false);
+}
+
+uint32_t WorkerDebuggerManager::GetDebuggersLength() const {
+ return mDebuggers.Length();
+}
+
+WorkerDebugger* WorkerDebuggerManager::GetDebuggerAt(uint32_t aIndex) const {
+ return mDebuggers.SafeElementAt(aIndex, nullptr);
+}
+
+nsTArray<nsCOMPtr<nsIWorkerDebuggerManagerListener>>
+WorkerDebuggerManager::CloneListeners() {
+ MutexAutoLock lock(mMutex);
+
+ return mListeners.Clone();
+}
+
+} // namespace mozilla::dom