Coverage Report

Created: 2018-09-25 14:53

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