WorkerDebuggerManager.cpp (10763B)
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/. */ 6 7 #include "WorkerDebuggerManager.h" 8 9 #include "WorkerDebugger.h" 10 #include "WorkerPrivate.h" 11 #include "mozilla/ClearOnShutdown.h" 12 #include "mozilla/Services.h" 13 #include "mozilla/StaticPtr.h" 14 #include "mozilla/dom/JSExecutionManager.h" 15 #include "nsIObserverService.h" 16 #include "nsSimpleEnumerator.h" 17 18 namespace mozilla::dom { 19 20 namespace { 21 22 class RegisterDebuggerMainThreadRunnable final : public mozilla::Runnable { 23 WorkerPrivate* mWorkerPrivate; 24 bool mNotifyListeners; 25 26 public: 27 RegisterDebuggerMainThreadRunnable(WorkerPrivate* aWorkerPrivate, 28 bool aNotifyListeners) 29 : mozilla::Runnable("RegisterDebuggerMainThreadRunnable"), 30 mWorkerPrivate(aWorkerPrivate), 31 mNotifyListeners(aNotifyListeners) {} 32 33 private: 34 ~RegisterDebuggerMainThreadRunnable() = default; 35 36 NS_IMETHOD 37 Run() override { 38 WorkerDebuggerManager* manager = WorkerDebuggerManager::Get(); 39 MOZ_ASSERT(manager); 40 41 manager->RegisterDebuggerMainThread(mWorkerPrivate, mNotifyListeners); 42 return NS_OK; 43 } 44 }; 45 46 class UnregisterDebuggerMainThreadRunnable final : public mozilla::Runnable { 47 WorkerPrivate* mWorkerPrivate; 48 49 public: 50 explicit UnregisterDebuggerMainThreadRunnable(WorkerPrivate* aWorkerPrivate) 51 : mozilla::Runnable("UnregisterDebuggerMainThreadRunnable"), 52 mWorkerPrivate(aWorkerPrivate) {} 53 54 private: 55 ~UnregisterDebuggerMainThreadRunnable() = default; 56 57 NS_IMETHOD 58 Run() override { 59 WorkerDebuggerManager* manager = WorkerDebuggerManager::Get(); 60 MOZ_ASSERT(manager); 61 62 manager->UnregisterDebuggerMainThread(mWorkerPrivate); 63 return NS_OK; 64 } 65 }; 66 67 static StaticRefPtr<WorkerDebuggerManager> gWorkerDebuggerManager; 68 69 } /* anonymous namespace */ 70 71 class WorkerDebuggerEnumerator final : public nsSimpleEnumerator { 72 nsTArray<nsCOMPtr<nsIWorkerDebugger>> mDebuggers; 73 uint32_t mIndex; 74 75 public: 76 explicit WorkerDebuggerEnumerator( 77 const nsTArray<nsCOMPtr<nsIWorkerDebugger>>& aDebuggers) 78 : mIndex(0) { 79 for (auto debugger : aDebuggers) { 80 bool isRemote; 81 (void)debugger->GetIsRemote(&isRemote); 82 if (!isRemote) { 83 mDebuggers.AppendElement(debugger); 84 } 85 } 86 } 87 88 NS_DECL_NSISIMPLEENUMERATOR 89 90 const nsID& DefaultInterface() override { 91 return NS_GET_IID(nsIWorkerDebugger); 92 } 93 94 private: 95 ~WorkerDebuggerEnumerator() override = default; 96 }; 97 98 NS_IMETHODIMP 99 WorkerDebuggerEnumerator::HasMoreElements(bool* aResult) { 100 *aResult = mIndex < mDebuggers.Length(); 101 return NS_OK; 102 }; 103 104 NS_IMETHODIMP 105 WorkerDebuggerEnumerator::GetNext(nsISupports** aResult) { 106 if (mIndex == mDebuggers.Length()) { 107 return NS_ERROR_FAILURE; 108 } 109 110 mDebuggers.ElementAt(mIndex++).forget(aResult); 111 return NS_OK; 112 }; 113 114 WorkerDebuggerManager::WorkerDebuggerManager() 115 : mMutex("WorkerDebuggerManager::mMutex") { 116 AssertIsOnMainThread(); 117 } 118 119 WorkerDebuggerManager::~WorkerDebuggerManager() { AssertIsOnMainThread(); } 120 121 // static 122 already_AddRefed<WorkerDebuggerManager> WorkerDebuggerManager::GetInstance() { 123 RefPtr<WorkerDebuggerManager> manager = WorkerDebuggerManager::GetOrCreate(); 124 return manager.forget(); 125 } 126 127 // static 128 WorkerDebuggerManager* WorkerDebuggerManager::GetOrCreate() { 129 AssertIsOnMainThread(); 130 131 if (!gWorkerDebuggerManager) { 132 // The observer service now owns us until shutdown. 133 gWorkerDebuggerManager = new WorkerDebuggerManager(); 134 if (NS_SUCCEEDED(gWorkerDebuggerManager->Init())) { 135 ClearOnShutdown(&gWorkerDebuggerManager); 136 } else { 137 NS_WARNING("Failed to initialize worker debugger manager!"); 138 gWorkerDebuggerManager = nullptr; 139 } 140 } 141 142 return gWorkerDebuggerManager; 143 } 144 145 WorkerDebuggerManager* WorkerDebuggerManager::Get() { 146 MOZ_ASSERT(gWorkerDebuggerManager); 147 return gWorkerDebuggerManager; 148 } 149 150 NS_IMPL_ISUPPORTS(WorkerDebuggerManager, nsIObserver, nsIWorkerDebuggerManager); 151 152 NS_IMETHODIMP 153 WorkerDebuggerManager::Observe(nsISupports* aSubject, const char* aTopic, 154 const char16_t* aData) { 155 if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { 156 Shutdown(); 157 return NS_OK; 158 } 159 160 MOZ_ASSERT_UNREACHABLE("Unknown observer topic!"); 161 return NS_OK; 162 } 163 164 NS_IMETHODIMP 165 WorkerDebuggerManager::GetWorkerDebuggerEnumerator( 166 nsISimpleEnumerator** aResult) { 167 AssertIsOnMainThread(); 168 169 RefPtr<WorkerDebuggerEnumerator> enumerator = 170 new WorkerDebuggerEnumerator(mDebuggers); 171 enumerator.forget(aResult); 172 return NS_OK; 173 } 174 175 NS_IMETHODIMP 176 WorkerDebuggerManager::AddListener( 177 nsIWorkerDebuggerManagerListener* aListener) { 178 AssertIsOnMainThread(); 179 180 MutexAutoLock lock(mMutex); 181 182 if (mListeners.Contains(aListener)) { 183 return NS_ERROR_INVALID_ARG; 184 } 185 186 mListeners.AppendElement(aListener); 187 return NS_OK; 188 } 189 190 NS_IMETHODIMP 191 WorkerDebuggerManager::RemoveListener( 192 nsIWorkerDebuggerManagerListener* aListener) { 193 AssertIsOnMainThread(); 194 195 MutexAutoLock lock(mMutex); 196 197 if (!mListeners.Contains(aListener)) { 198 return NS_OK; 199 } 200 201 mListeners.RemoveElement(aListener); 202 return NS_OK; 203 } 204 205 nsresult WorkerDebuggerManager::Init() { 206 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 207 NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE); 208 209 nsresult rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); 210 NS_ENSURE_SUCCESS(rv, rv); 211 212 return NS_OK; 213 } 214 215 void WorkerDebuggerManager::Shutdown() { 216 AssertIsOnMainThread(); 217 218 MutexAutoLock lock(mMutex); 219 220 mListeners.Clear(); 221 } 222 223 void WorkerDebuggerManager::RegisterDebugger(WorkerPrivate* aWorkerPrivate) { 224 aWorkerPrivate->AssertIsOnParentThread(); 225 226 if (NS_IsMainThread()) { 227 // When the parent thread is the main thread, it will always block until all 228 // register liseners have been called, since it cannot continue until the 229 // call to RegisterDebuggerMainThread returns. 230 // 231 // In this case, it is always safe to notify all listeners on the main 232 // thread, even if there were no listeners at the time this method was 233 // called, so we can always pass true for the value of aNotifyListeners. 234 // This avoids having to lock mMutex to check whether mListeners is empty. 235 RegisterDebuggerMainThread(aWorkerPrivate, true); 236 } else { 237 // We guarantee that if any register listeners are called, the worker does 238 // not start running until all register listeners have been called. To 239 // guarantee this, the parent thread should block until all register 240 // listeners have been called. 241 // 242 // However, to avoid overhead when the debugger is not being used, the 243 // parent thread will only block if there were any listeners at the time 244 // this method was called. As a result, we should not notify any listeners 245 // on the main thread if there were no listeners at the time this method was 246 // called, because the parent will not be blocking in that case. 247 bool hasListeners = false; 248 { 249 MutexAutoLock lock(mMutex); 250 251 hasListeners = !mListeners.IsEmpty(); 252 } 253 254 nsCOMPtr<nsIRunnable> runnable = 255 new RegisterDebuggerMainThreadRunnable(aWorkerPrivate, hasListeners); 256 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL)); 257 258 if (hasListeners) { 259 aWorkerPrivate->WaitForIsDebuggerRegistered(true); 260 } 261 } 262 } 263 264 void WorkerDebuggerManager::UnregisterDebugger(WorkerPrivate* aWorkerPrivate) { 265 aWorkerPrivate->AssertIsOnParentThread(); 266 267 if (NS_IsMainThread()) { 268 UnregisterDebuggerMainThread(aWorkerPrivate); 269 } else { 270 nsCOMPtr<nsIRunnable> runnable = 271 new UnregisterDebuggerMainThreadRunnable(aWorkerPrivate); 272 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL)); 273 274 aWorkerPrivate->WaitForIsDebuggerRegistered(false); 275 } 276 } 277 278 void WorkerDebuggerManager::RegisterDebugger( 279 nsIWorkerDebugger* aRemoteWorkerDebugger) { 280 MOZ_ASSERT_DEBUG_OR_FUZZING(XRE_IsParentProcess()); 281 AssertIsOnMainThread(); 282 283 mDebuggers.AppendElement(aRemoteWorkerDebugger); 284 // for (const auto& listener : CloneListeners()) { 285 // listener->OnRegister(aRemoteWorkerDebugger); 286 // } 287 } 288 289 void WorkerDebuggerManager::UnregisterDebugger( 290 nsIWorkerDebugger* aRemoteWorkerDebugger) { 291 MOZ_ASSERT_DEBUG_OR_FUZZING(XRE_IsParentProcess()); 292 AssertIsOnMainThread(); 293 294 mDebuggers.RemoveElement(aRemoteWorkerDebugger); 295 // for (const auto& listener : CloneListeners()) { 296 // listener->OnUnregister(aRemoteWorkerDebugger); 297 // } 298 } 299 300 void WorkerDebuggerManager::RegisterDebuggerMainThread( 301 WorkerPrivate* aWorkerPrivate, bool aNotifyListeners) { 302 AssertIsOnMainThread(); 303 304 RefPtr<WorkerDebugger> debugger = new WorkerDebugger(aWorkerPrivate); 305 mDebuggers.AppendElement(debugger); 306 307 aWorkerPrivate->SetDebugger(debugger); 308 309 if (aNotifyListeners) { 310 for (const auto& listener : CloneListeners()) { 311 listener->OnRegister(debugger); 312 } 313 } 314 315 aWorkerPrivate->SetIsDebuggerRegistered(true); 316 } 317 318 void WorkerDebuggerManager::UnregisterDebuggerMainThread( 319 WorkerPrivate* aWorkerPrivate) { 320 AssertIsOnMainThread(); 321 322 // There is nothing to do here if the debugger was never succesfully 323 // registered. We need to check this on the main thread because the worker 324 // does not wait for the registration to complete if there were no listeners 325 // installed when it started. 326 if (!aWorkerPrivate->IsDebuggerRegistered()) { 327 return; 328 } 329 330 RefPtr<WorkerDebugger> debugger = aWorkerPrivate->Debugger(); 331 mDebuggers.RemoveElement(debugger); 332 333 aWorkerPrivate->SetDebugger(nullptr); 334 335 for (const auto& listener : CloneListeners()) { 336 listener->OnUnregister(debugger); 337 } 338 339 debugger->Close(); 340 aWorkerPrivate->SetIsDebuggerRegistered(false); 341 } 342 343 uint32_t WorkerDebuggerManager::GetDebuggersLength() const { 344 return mDebuggers.Length(); 345 } 346 347 nsIWorkerDebugger* WorkerDebuggerManager::GetDebuggerAt(uint32_t aIndex) const { 348 return mDebuggers.SafeElementAt(aIndex, nullptr); 349 } 350 351 nsCOMPtr<nsIWorkerDebugger> WorkerDebuggerManager::GetDebuggerById( 352 const nsString& aWorkerId) { 353 MOZ_ASSERT_DEBUG_OR_FUZZING(!aWorkerId.IsEmpty()); 354 for (auto debugger : mDebuggers) { 355 nsAutoString workerId; 356 bool isRemote; 357 debugger->GetId(workerId); 358 debugger->GetIsRemote(&isRemote); 359 if (workerId.Equals(aWorkerId) && isRemote) { 360 return debugger; 361 } 362 } 363 return nullptr; 364 } 365 366 nsTArray<nsCOMPtr<nsIWorkerDebuggerManagerListener>> 367 WorkerDebuggerManager::CloneListeners() { 368 MutexAutoLock lock(mMutex); 369 370 return mListeners.Clone(); 371 } 372 373 } // namespace mozilla::dom