1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "WorkerDebuggerManager.h"
9 #include "nsSimpleEnumerator.h"
11 #include "mozilla/dom/JSExecutionManager.h"
12 #include "mozilla/ClearOnShutdown.h"
13 #include "mozilla/Services.h"
14 #include "mozilla/StaticPtr.h"
16 #include "WorkerDebugger.h"
17 #include "WorkerPrivate.h"
18 #include "nsIObserverService.h"
20 namespace mozilla::dom
{
24 class RegisterDebuggerMainThreadRunnable final
: public mozilla::Runnable
{
25 WorkerPrivate
* mWorkerPrivate
;
26 bool mNotifyListeners
;
29 RegisterDebuggerMainThreadRunnable(WorkerPrivate
* aWorkerPrivate
,
30 bool aNotifyListeners
)
31 : mozilla::Runnable("RegisterDebuggerMainThreadRunnable"),
32 mWorkerPrivate(aWorkerPrivate
),
33 mNotifyListeners(aNotifyListeners
) {}
36 ~RegisterDebuggerMainThreadRunnable() = default;
40 WorkerDebuggerManager
* manager
= WorkerDebuggerManager::Get();
43 manager
->RegisterDebuggerMainThread(mWorkerPrivate
, mNotifyListeners
);
48 class UnregisterDebuggerMainThreadRunnable final
: public mozilla::Runnable
{
49 WorkerPrivate
* mWorkerPrivate
;
52 explicit UnregisterDebuggerMainThreadRunnable(WorkerPrivate
* aWorkerPrivate
)
53 : mozilla::Runnable("UnregisterDebuggerMainThreadRunnable"),
54 mWorkerPrivate(aWorkerPrivate
) {}
57 ~UnregisterDebuggerMainThreadRunnable() = default;
61 WorkerDebuggerManager
* manager
= WorkerDebuggerManager::Get();
64 manager
->UnregisterDebuggerMainThread(mWorkerPrivate
);
69 static StaticRefPtr
<WorkerDebuggerManager
> gWorkerDebuggerManager
;
71 } /* anonymous namespace */
73 class WorkerDebuggerEnumerator final
: public nsSimpleEnumerator
{
74 nsTArray
<RefPtr
<WorkerDebugger
>> mDebuggers
;
78 explicit WorkerDebuggerEnumerator(
79 const nsTArray
<RefPtr
<WorkerDebugger
>>& aDebuggers
)
80 : mDebuggers(aDebuggers
.Clone()), mIndex(0) {}
82 NS_DECL_NSISIMPLEENUMERATOR
84 const nsID
& DefaultInterface() override
{
85 return NS_GET_IID(nsIWorkerDebugger
);
89 ~WorkerDebuggerEnumerator() override
= default;
93 WorkerDebuggerEnumerator::HasMoreElements(bool* aResult
) {
94 *aResult
= mIndex
< mDebuggers
.Length();
99 WorkerDebuggerEnumerator::GetNext(nsISupports
** aResult
) {
100 if (mIndex
== mDebuggers
.Length()) {
101 return NS_ERROR_FAILURE
;
104 mDebuggers
.ElementAt(mIndex
++).forget(aResult
);
108 WorkerDebuggerManager::WorkerDebuggerManager()
109 : mMutex("WorkerDebuggerManager::mMutex") {
110 AssertIsOnMainThread();
113 WorkerDebuggerManager::~WorkerDebuggerManager() { AssertIsOnMainThread(); }
116 already_AddRefed
<WorkerDebuggerManager
> WorkerDebuggerManager::GetInstance() {
117 RefPtr
<WorkerDebuggerManager
> manager
= WorkerDebuggerManager::GetOrCreate();
118 return manager
.forget();
122 WorkerDebuggerManager
* WorkerDebuggerManager::GetOrCreate() {
123 AssertIsOnMainThread();
125 if (!gWorkerDebuggerManager
) {
126 // The observer service now owns us until shutdown.
127 gWorkerDebuggerManager
= new WorkerDebuggerManager();
128 if (NS_SUCCEEDED(gWorkerDebuggerManager
->Init())) {
129 ClearOnShutdown(&gWorkerDebuggerManager
);
131 NS_WARNING("Failed to initialize worker debugger manager!");
132 gWorkerDebuggerManager
= nullptr;
136 return gWorkerDebuggerManager
;
139 WorkerDebuggerManager
* WorkerDebuggerManager::Get() {
140 MOZ_ASSERT(gWorkerDebuggerManager
);
141 return gWorkerDebuggerManager
;
144 NS_IMPL_ISUPPORTS(WorkerDebuggerManager
, nsIObserver
, nsIWorkerDebuggerManager
);
147 WorkerDebuggerManager::Observe(nsISupports
* aSubject
, const char* aTopic
,
148 const char16_t
* aData
) {
149 if (!strcmp(aTopic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
)) {
154 MOZ_ASSERT_UNREACHABLE("Unknown observer topic!");
159 WorkerDebuggerManager::GetWorkerDebuggerEnumerator(
160 nsISimpleEnumerator
** aResult
) {
161 AssertIsOnMainThread();
163 RefPtr
<WorkerDebuggerEnumerator
> enumerator
=
164 new WorkerDebuggerEnumerator(mDebuggers
);
165 enumerator
.forget(aResult
);
170 WorkerDebuggerManager::AddListener(
171 nsIWorkerDebuggerManagerListener
* aListener
) {
172 AssertIsOnMainThread();
174 MutexAutoLock
lock(mMutex
);
176 if (mListeners
.Contains(aListener
)) {
177 return NS_ERROR_INVALID_ARG
;
180 mListeners
.AppendElement(aListener
);
185 WorkerDebuggerManager::RemoveListener(
186 nsIWorkerDebuggerManagerListener
* aListener
) {
187 AssertIsOnMainThread();
189 MutexAutoLock
lock(mMutex
);
191 if (!mListeners
.Contains(aListener
)) {
195 mListeners
.RemoveElement(aListener
);
199 nsresult
WorkerDebuggerManager::Init() {
200 nsCOMPtr
<nsIObserverService
> obs
= services::GetObserverService();
201 NS_ENSURE_TRUE(obs
, NS_ERROR_FAILURE
);
203 nsresult rv
= obs
->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
, false);
204 NS_ENSURE_SUCCESS(rv
, rv
);
209 void WorkerDebuggerManager::Shutdown() {
210 AssertIsOnMainThread();
212 MutexAutoLock
lock(mMutex
);
217 void WorkerDebuggerManager::RegisterDebugger(WorkerPrivate
* aWorkerPrivate
) {
218 aWorkerPrivate
->AssertIsOnParentThread();
220 if (NS_IsMainThread()) {
221 // When the parent thread is the main thread, it will always block until all
222 // register liseners have been called, since it cannot continue until the
223 // call to RegisterDebuggerMainThread returns.
225 // In this case, it is always safe to notify all listeners on the main
226 // thread, even if there were no listeners at the time this method was
227 // called, so we can always pass true for the value of aNotifyListeners.
228 // This avoids having to lock mMutex to check whether mListeners is empty.
229 RegisterDebuggerMainThread(aWorkerPrivate
, true);
231 // We guarantee that if any register listeners are called, the worker does
232 // not start running until all register listeners have been called. To
233 // guarantee this, the parent thread should block until all register
234 // listeners have been called.
236 // However, to avoid overhead when the debugger is not being used, the
237 // parent thread will only block if there were any listeners at the time
238 // this method was called. As a result, we should not notify any listeners
239 // on the main thread if there were no listeners at the time this method was
240 // called, because the parent will not be blocking in that case.
241 bool hasListeners
= false;
243 MutexAutoLock
lock(mMutex
);
245 hasListeners
= !mListeners
.IsEmpty();
248 nsCOMPtr
<nsIRunnable
> runnable
=
249 new RegisterDebuggerMainThreadRunnable(aWorkerPrivate
, hasListeners
);
250 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable
, NS_DISPATCH_NORMAL
));
253 aWorkerPrivate
->WaitForIsDebuggerRegistered(true);
258 void WorkerDebuggerManager::UnregisterDebugger(WorkerPrivate
* aWorkerPrivate
) {
259 aWorkerPrivate
->AssertIsOnParentThread();
261 if (NS_IsMainThread()) {
262 UnregisterDebuggerMainThread(aWorkerPrivate
);
264 nsCOMPtr
<nsIRunnable
> runnable
=
265 new UnregisterDebuggerMainThreadRunnable(aWorkerPrivate
);
266 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable
, NS_DISPATCH_NORMAL
));
268 aWorkerPrivate
->WaitForIsDebuggerRegistered(false);
272 void WorkerDebuggerManager::RegisterDebuggerMainThread(
273 WorkerPrivate
* aWorkerPrivate
, bool aNotifyListeners
) {
274 AssertIsOnMainThread();
276 RefPtr
<WorkerDebugger
> debugger
= new WorkerDebugger(aWorkerPrivate
);
277 mDebuggers
.AppendElement(debugger
);
279 aWorkerPrivate
->SetDebugger(debugger
);
281 if (aNotifyListeners
) {
282 for (const auto& listener
: CloneListeners()) {
283 listener
->OnRegister(debugger
);
287 aWorkerPrivate
->SetIsDebuggerRegistered(true);
290 void WorkerDebuggerManager::UnregisterDebuggerMainThread(
291 WorkerPrivate
* aWorkerPrivate
) {
292 AssertIsOnMainThread();
294 // There is nothing to do here if the debugger was never succesfully
295 // registered. We need to check this on the main thread because the worker
296 // does not wait for the registration to complete if there were no listeners
297 // installed when it started.
298 if (!aWorkerPrivate
->IsDebuggerRegistered()) {
302 RefPtr
<WorkerDebugger
> debugger
= aWorkerPrivate
->Debugger();
303 mDebuggers
.RemoveElement(debugger
);
305 aWorkerPrivate
->SetDebugger(nullptr);
307 for (const auto& listener
: CloneListeners()) {
308 listener
->OnUnregister(debugger
);
312 aWorkerPrivate
->SetIsDebuggerRegistered(false);
315 uint32_t WorkerDebuggerManager::GetDebuggersLength() const {
316 return mDebuggers
.Length();
319 WorkerDebugger
* WorkerDebuggerManager::GetDebuggerAt(uint32_t aIndex
) const {
320 return mDebuggers
.SafeElementAt(aIndex
, nullptr);
323 nsTArray
<nsCOMPtr
<nsIWorkerDebuggerManagerListener
>>
324 WorkerDebuggerManager::CloneListeners() {
325 MutexAutoLock
lock(mMutex
);
327 return mListeners
.Clone();
330 } // namespace mozilla::dom