tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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