Bug 1771337 [wpt PR 34217] - Convert `popup=popup` to `popup=auto` or just `popup...
[gecko.git] / dom / workers / WorkerDebuggerManager.cpp
blobdfca4748b7c228174cae037ba445c201eb3f1765
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 {
22 namespace {
24 class RegisterDebuggerMainThreadRunnable final : public mozilla::Runnable {
25 WorkerPrivate* mWorkerPrivate;
26 bool mNotifyListeners;
28 public:
29 RegisterDebuggerMainThreadRunnable(WorkerPrivate* aWorkerPrivate,
30 bool aNotifyListeners)
31 : mozilla::Runnable("RegisterDebuggerMainThreadRunnable"),
32 mWorkerPrivate(aWorkerPrivate),
33 mNotifyListeners(aNotifyListeners) {}
35 private:
36 ~RegisterDebuggerMainThreadRunnable() = default;
38 NS_IMETHOD
39 Run() override {
40 WorkerDebuggerManager* manager = WorkerDebuggerManager::Get();
41 MOZ_ASSERT(manager);
43 manager->RegisterDebuggerMainThread(mWorkerPrivate, mNotifyListeners);
44 return NS_OK;
48 class UnregisterDebuggerMainThreadRunnable final : public mozilla::Runnable {
49 WorkerPrivate* mWorkerPrivate;
51 public:
52 explicit UnregisterDebuggerMainThreadRunnable(WorkerPrivate* aWorkerPrivate)
53 : mozilla::Runnable("UnregisterDebuggerMainThreadRunnable"),
54 mWorkerPrivate(aWorkerPrivate) {}
56 private:
57 ~UnregisterDebuggerMainThreadRunnable() = default;
59 NS_IMETHOD
60 Run() override {
61 WorkerDebuggerManager* manager = WorkerDebuggerManager::Get();
62 MOZ_ASSERT(manager);
64 manager->UnregisterDebuggerMainThread(mWorkerPrivate);
65 return NS_OK;
69 static StaticRefPtr<WorkerDebuggerManager> gWorkerDebuggerManager;
71 } /* anonymous namespace */
73 class WorkerDebuggerEnumerator final : public nsSimpleEnumerator {
74 nsTArray<RefPtr<WorkerDebugger>> mDebuggers;
75 uint32_t mIndex;
77 public:
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);
88 private:
89 ~WorkerDebuggerEnumerator() override = default;
92 NS_IMETHODIMP
93 WorkerDebuggerEnumerator::HasMoreElements(bool* aResult) {
94 *aResult = mIndex < mDebuggers.Length();
95 return NS_OK;
98 NS_IMETHODIMP
99 WorkerDebuggerEnumerator::GetNext(nsISupports** aResult) {
100 if (mIndex == mDebuggers.Length()) {
101 return NS_ERROR_FAILURE;
104 mDebuggers.ElementAt(mIndex++).forget(aResult);
105 return NS_OK;
108 WorkerDebuggerManager::WorkerDebuggerManager()
109 : mMutex("WorkerDebuggerManager::mMutex") {
110 AssertIsOnMainThread();
113 WorkerDebuggerManager::~WorkerDebuggerManager() { AssertIsOnMainThread(); }
115 // static
116 already_AddRefed<WorkerDebuggerManager> WorkerDebuggerManager::GetInstance() {
117 RefPtr<WorkerDebuggerManager> manager = WorkerDebuggerManager::GetOrCreate();
118 return manager.forget();
121 // static
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);
130 } else {
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);
146 NS_IMETHODIMP
147 WorkerDebuggerManager::Observe(nsISupports* aSubject, const char* aTopic,
148 const char16_t* aData) {
149 if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
150 Shutdown();
151 return NS_OK;
154 MOZ_ASSERT_UNREACHABLE("Unknown observer topic!");
155 return NS_OK;
158 NS_IMETHODIMP
159 WorkerDebuggerManager::GetWorkerDebuggerEnumerator(
160 nsISimpleEnumerator** aResult) {
161 AssertIsOnMainThread();
163 RefPtr<WorkerDebuggerEnumerator> enumerator =
164 new WorkerDebuggerEnumerator(mDebuggers);
165 enumerator.forget(aResult);
166 return NS_OK;
169 NS_IMETHODIMP
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);
181 return NS_OK;
184 NS_IMETHODIMP
185 WorkerDebuggerManager::RemoveListener(
186 nsIWorkerDebuggerManagerListener* aListener) {
187 AssertIsOnMainThread();
189 MutexAutoLock lock(mMutex);
191 if (!mListeners.Contains(aListener)) {
192 return NS_OK;
195 mListeners.RemoveElement(aListener);
196 return NS_OK;
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);
206 return NS_OK;
209 void WorkerDebuggerManager::Shutdown() {
210 AssertIsOnMainThread();
212 MutexAutoLock lock(mMutex);
214 mListeners.Clear();
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);
230 } else {
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));
252 if (hasListeners) {
253 aWorkerPrivate->WaitForIsDebuggerRegistered(true);
258 void WorkerDebuggerManager::UnregisterDebugger(WorkerPrivate* aWorkerPrivate) {
259 aWorkerPrivate->AssertIsOnParentThread();
261 if (NS_IsMainThread()) {
262 UnregisterDebuggerMainThread(aWorkerPrivate);
263 } else {
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()) {
299 return;
302 RefPtr<WorkerDebugger> debugger = aWorkerPrivate->Debugger();
303 mDebuggers.RemoveElement(debugger);
305 aWorkerPrivate->SetDebugger(nullptr);
307 for (const auto& listener : CloneListeners()) {
308 listener->OnUnregister(debugger);
311 debugger->Close();
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