/* -*- 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 gWorkerDebuggerManager; } /* anonymous namespace */ class WorkerDebuggerEnumerator final : public nsSimpleEnumerator { nsTArray> mDebuggers; uint32_t mIndex; public: explicit WorkerDebuggerEnumerator( const nsTArray>& 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::GetInstance() { RefPtr 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 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 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 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 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 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 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> WorkerDebuggerManager::CloneListeners() { MutexAutoLock lock(mMutex); return mListeners.Clone(); } } // namespace mozilla::dom