Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/worklet/WorkletThread.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 "WorkletThread.h"
8
#include "prthread.h"
9
#include "nsContentUtils.h"
10
#include "nsCycleCollector.h"
11
#include "mozilla/dom/AtomList.h"
12
#include "mozilla/EventQueue.h"
13
#include "mozilla/ThreadEventQueue.h"
14
15
namespace mozilla {
16
namespace dom {
17
18
namespace {
19
20
// The size of the worklet runtime heaps in bytes.
21
0
#define WORKLET_DEFAULT_RUNTIME_HEAPSIZE 32 * 1024 * 1024
22
23
// The size of the generational GC nursery for worklet, in bytes.
24
0
#define WORKLET_DEFAULT_NURSERY_SIZE 1 * 1024 * 1024
25
26
// The C stack size. We use the same stack size on all platforms for
27
// consistency.
28
const uint32_t kWorkletStackSize = 256 * sizeof(size_t) * 1024;
29
30
// Helper functions
31
32
bool
33
PreserveWrapper(JSContext* aCx, JS::HandleObject aObj)
34
0
{
35
0
  MOZ_ASSERT(aCx);
36
0
  MOZ_ASSERT(aObj);
37
0
  MOZ_ASSERT(mozilla::dom::IsDOMObject(aObj));
38
0
  return mozilla::dom::TryPreserveWrapper(aObj);
39
0
}
40
41
void
42
DestroyWorkletPrincipals(JSPrincipals* aPrincipals)
43
0
{
44
0
  MOZ_ASSERT_UNREACHABLE("Worklet principals refcount should never fall below one");
45
0
}
46
47
JSObject*
48
Wrap(JSContext* aCx, JS::HandleObject aExisting, JS::HandleObject aObj)
49
0
{
50
0
  if (aExisting) {
51
0
    js::Wrapper::Renew(aExisting, aObj,
52
0
                       &js::OpaqueCrossCompartmentWrapper::singleton);
53
0
  }
54
0
55
0
  return js::Wrapper::New(aCx, aObj,
56
0
                          &js::OpaqueCrossCompartmentWrapper::singleton);
57
0
}
58
59
const JSWrapObjectCallbacks WrapObjectCallbacks =
60
{
61
  Wrap,
62
  nullptr,
63
};
64
65
} // namespace
66
67
// This classes control CC in the worklet thread.
68
69
class WorkletJSRuntime final : public mozilla::CycleCollectedJSRuntime
70
{
71
public:
72
  explicit WorkletJSRuntime(JSContext* aCx)
73
    : CycleCollectedJSRuntime(aCx)
74
0
  {
75
0
  }
76
77
  ~WorkletJSRuntime() override = default;
78
79
  virtual void
80
  PrepareForForgetSkippable() override
81
0
  {
82
0
  }
83
84
  virtual void
85
  BeginCycleCollectionCallback() override
86
0
  {
87
0
  }
88
89
  virtual void
90
  EndCycleCollectionCallback(CycleCollectorResults& aResults) override
91
0
  {
92
0
  }
93
94
  virtual void
95
  DispatchDeferredDeletion(bool aContinuation, bool aPurge) override
96
0
  {
97
0
    MOZ_ASSERT(!aContinuation);
98
0
    nsCycleCollector_doDeferredDeletion();
99
0
  }
100
101
  virtual void
102
  CustomGCCallback(JSGCStatus aStatus) override
103
0
  {
104
0
    // nsCycleCollector_collect() requires a cycle collector but
105
0
    // ~WorkletJSContext calls nsCycleCollector_shutdown() and the base class
106
0
    // destructor will trigger a final GC.  The nsCycleCollector_collect()
107
0
    // call can be skipped in this GC as ~CycleCollectedJSContext removes the
108
0
    // context from |this|.
109
0
    if (aStatus == JSGC_END && !Contexts().isEmpty()) {
110
0
      nsCycleCollector_collect(nullptr);
111
0
    }
112
0
  }
113
};
114
115
class WorkletJSContext final : public CycleCollectedJSContext
116
{
117
public:
118
  explicit WorkletJSContext(WorkletThread* aWorkletThread)
119
    : mWorkletThread(aWorkletThread)
120
0
  {
121
0
    MOZ_ASSERT(aWorkletThread);
122
0
    MOZ_ASSERT(!NS_IsMainThread());
123
0
124
0
    nsCycleCollector_startup();
125
0
  }
126
127
  ~WorkletJSContext() override
128
0
  {
129
0
    MOZ_ASSERT(!NS_IsMainThread());
130
0
131
0
    JSContext* cx = MaybeContext();
132
0
    if (!cx) {
133
0
      return;   // Initialize() must have failed
134
0
    }
135
0
136
0
    nsCycleCollector_shutdown();
137
0
  }
138
139
0
  WorkletJSContext* GetAsWorkletJSContext() override { return this; }
140
141
  CycleCollectedJSRuntime* CreateRuntime(JSContext* aCx) override
142
0
  {
143
0
    return new WorkletJSRuntime(aCx);
144
0
  }
145
146
  nsresult
147
  Initialize(JSRuntime* aParentRuntime)
148
0
  {
149
0
    MOZ_ASSERT(!NS_IsMainThread());
150
0
151
0
    nsresult rv =
152
0
      CycleCollectedJSContext::Initialize(aParentRuntime,
153
0
                                          WORKLET_DEFAULT_RUNTIME_HEAPSIZE,
154
0
                                          WORKLET_DEFAULT_NURSERY_SIZE);
155
0
     if (NS_WARN_IF(NS_FAILED(rv))) {
156
0
       return rv;
157
0
     }
158
0
159
0
    JSContext* cx = Context();
160
0
161
0
    js::SetPreserveWrapperCallback(cx, PreserveWrapper);
162
0
    JS_InitDestroyPrincipalsCallback(cx, DestroyWorkletPrincipals);
163
0
    JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks);
164
0
    JS_SetFutexCanWait(cx);
165
0
166
0
    return NS_OK;
167
0
  }
168
169
  void
170
  DispatchToMicroTask(already_AddRefed<MicroTaskRunnable> aRunnable) override
171
0
  {
172
0
    RefPtr<MicroTaskRunnable> runnable(aRunnable);
173
0
174
0
    MOZ_ASSERT(!NS_IsMainThread());
175
0
    MOZ_ASSERT(runnable);
176
0
177
0
    WorkletThread* workletThread = WorkletThread::Get();
178
0
    MOZ_ASSERT(workletThread);
179
0
180
0
    JSContext* cx = workletThread->GetJSContext();
181
0
    MOZ_ASSERT(cx);
182
0
183
#ifdef DEBUG
184
    JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
185
    MOZ_ASSERT(global);
186
#endif
187
188
0
    JS::JobQueueMayNotBeEmpty(cx);
189
0
    GetMicroTaskQueue().push(runnable.forget());
190
0
  }
191
192
  WorkletThread* GetWorkletThread() const
193
0
  {
194
0
    return mWorkletThread;
195
0
  }
196
197
  bool IsSystemCaller() const override
198
0
  {
199
0
    // Currently no support for special system worklet privileges.
200
0
    return false;
201
0
  }
202
203
private:
204
  RefPtr<WorkletThread> mWorkletThread;
205
};
206
207
// This is the first runnable to be dispatched. It calls the RunEventLoop() so
208
// basically everything happens into this runnable. The reason behind this
209
// approach is that, when the Worklet is terminated, it must not have any JS in
210
// stack, but, because we have CC, nsIThread creates an AutoNoJSAPI object by
211
// default. Using this runnable, CC exists only into it.
212
class WorkletThread::PrimaryRunnable final : public Runnable
213
{
214
public:
215
  explicit PrimaryRunnable(WorkletThread* aWorkletThread)
216
    : Runnable("WorkletThread::PrimaryRunnable")
217
    , mWorkletThread(aWorkletThread)
218
0
  {
219
0
    MOZ_ASSERT(aWorkletThread);
220
0
    MOZ_ASSERT(NS_IsMainThread());
221
0
222
0
    mParentRuntime =
223
0
      JS_GetParentRuntime(CycleCollectedJSContext::Get()->Context());
224
0
    MOZ_ASSERT(mParentRuntime);
225
0
  }
226
227
  NS_IMETHOD
228
  Run() override
229
0
  {
230
0
    mWorkletThread->RunEventLoop(mParentRuntime);
231
0
    return NS_OK;
232
0
  }
233
234
private:
235
  RefPtr<WorkletThread> mWorkletThread;
236
  JSRuntime* mParentRuntime;
237
};
238
239
// This is the last runnable to be dispatched. It calls the TerminateInternal()
240
class WorkletThread::TerminateRunnable final : public Runnable
241
{
242
public:
243
  explicit TerminateRunnable(WorkletThread* aWorkletThread)
244
    : Runnable("WorkletThread::TerminateRunnable")
245
    , mWorkletThread(aWorkletThread)
246
0
  {
247
0
    MOZ_ASSERT(aWorkletThread);
248
0
    MOZ_ASSERT(NS_IsMainThread());
249
0
  }
250
251
  NS_IMETHOD
252
  Run() override
253
0
  {
254
0
    mWorkletThread->TerminateInternal();
255
0
    return NS_OK;
256
0
  }
257
258
private:
259
  RefPtr<WorkletThread> mWorkletThread;
260
};
261
262
WorkletThread::WorkletThread(const WorkletLoadInfo& aWorkletLoadInfo)
263
  : nsThread(MakeNotNull<ThreadEventQueue<mozilla::EventQueue>*>(
264
               MakeUnique<mozilla::EventQueue>()),
265
             nsThread::NOT_MAIN_THREAD, kWorkletStackSize)
266
  , mWorkletLoadInfo(aWorkletLoadInfo)
267
  , mCreationTimeStamp(TimeStamp::Now())
268
  , mJSContext(nullptr)
269
  , mIsTerminating(false)
270
0
{
271
0
  MOZ_ASSERT(NS_IsMainThread());
272
0
  nsContentUtils::RegisterShutdownObserver(this);
273
0
}
274
275
WorkletThread::~WorkletThread()
276
0
{
277
0
  // This should be gone during the termination step.
278
0
  MOZ_ASSERT(!mJSContext);
279
0
}
280
281
// static
282
already_AddRefed<WorkletThread>
283
WorkletThread::Create(const WorkletLoadInfo& aWorkletLoadInfo)
284
0
{
285
0
  RefPtr<WorkletThread> thread =
286
0
    new WorkletThread(aWorkletLoadInfo);
287
0
  if (NS_WARN_IF(NS_FAILED(thread->Init()))) {
288
0
    return nullptr;
289
0
  }
290
0
291
0
  RefPtr<PrimaryRunnable> runnable = new PrimaryRunnable(thread);
292
0
  if (NS_WARN_IF(NS_FAILED(thread->DispatchRunnable(runnable.forget())))) {
293
0
    return nullptr;
294
0
  }
295
0
296
0
  return thread.forget();
297
0
}
298
299
nsresult
300
WorkletThread::DispatchRunnable(already_AddRefed<nsIRunnable> aRunnable)
301
0
{
302
0
  nsCOMPtr<nsIRunnable> runnable(aRunnable);
303
0
  return nsThread::Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
304
0
}
305
306
NS_IMETHODIMP
307
WorkletThread::DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags)
308
0
{
309
0
  nsCOMPtr<nsIRunnable> runnable(aRunnable);
310
0
  return Dispatch(runnable.forget(), aFlags);
311
0
}
312
313
NS_IMETHODIMP
314
WorkletThread::Dispatch(already_AddRefed<nsIRunnable> aRunnable,
315
                        uint32_t aFlags)
316
0
{
317
0
  nsCOMPtr<nsIRunnable> runnable(aRunnable);
318
0
319
0
  // Worklet only supports asynchronous dispatch.
320
0
  if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) {
321
0
    return NS_ERROR_UNEXPECTED;
322
0
  }
323
0
324
0
  return nsThread::Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
325
0
}
326
327
NS_IMETHODIMP
328
WorkletThread::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t aFlags)
329
0
{
330
0
  return NS_ERROR_NOT_IMPLEMENTED;
331
0
}
332
333
void
334
WorkletThread::RunEventLoop(JSRuntime* aParentRuntime)
335
0
{
336
0
  MOZ_ASSERT(!NS_IsMainThread());
337
0
338
0
  PR_SetCurrentThreadName("worklet");
339
0
340
0
  auto context = MakeUnique<WorkletJSContext>(this);
341
0
  nsresult rv = context->Initialize(aParentRuntime);
342
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
343
0
    // TODO: error propagation
344
0
    return;
345
0
  }
346
0
347
0
  // FIXME: JS_SetDefaultLocale
348
0
  // FIXME: JSSettings
349
0
  // FIXME: JS_SetNativeStackQuota
350
0
  // FIXME: JS_SetSecurityCallbacks
351
0
  // FIXME: JS::SetAsmJSCacheOps
352
0
  // FIXME: JS::SetAsyncTaskCallbacks
353
0
  // FIXME: JS_AddInterruptCallback
354
0
  // FIXME: JS::SetCTypesActivityCallback
355
0
  // FIXME: JS_SetGCZeal
356
0
357
0
  if (!JS::InitSelfHostedCode(context->Context())) {
358
0
    // TODO: error propagation
359
0
    return;
360
0
  }
361
0
362
0
  mJSContext = context->Context();
363
0
364
0
  while (mJSContext) {
365
0
    MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(this, /* wait: */ true));
366
0
  }
367
0
368
0
  MOZ_ASSERT(mJSContext == nullptr);
369
0
}
370
371
void
372
WorkletThread::Terminate()
373
0
{
374
0
  MOZ_ASSERT(NS_IsMainThread());
375
0
376
0
  if (mIsTerminating) {
377
0
    // nsThread::Dispatch() would leak the runnable if the event queue is no
378
0
    // longer accepting runnables.
379
0
    return;
380
0
  }
381
0
382
0
  mIsTerminating = true;
383
0
384
0
  nsContentUtils::UnregisterShutdownObserver(this);
385
0
386
0
  RefPtr<TerminateRunnable> runnable = new TerminateRunnable(this);
387
0
  DispatchRunnable(runnable.forget());
388
0
}
389
390
void
391
WorkletThread::TerminateInternal()
392
0
{
393
0
  AssertIsOnWorkletThread();
394
0
395
0
  mJSContext = nullptr;
396
0
397
0
  nsCOMPtr<nsIRunnable> runnable =
398
0
    NewRunnableMethod("WorkletThread::Shutdown", this,
399
0
                      &WorkletThread::Shutdown);
400
0
  NS_DispatchToMainThread(runnable);
401
0
}
402
403
JSContext*
404
WorkletThread::GetJSContext() const
405
0
{
406
0
  AssertIsOnWorkletThread();
407
0
  MOZ_ASSERT(mJSContext);
408
0
  return mJSContext;
409
0
}
410
411
const WorkletLoadInfo&
412
WorkletThread::GetWorkletLoadInfo() const
413
0
{
414
0
  return mWorkletLoadInfo;
415
0
}
416
417
/* static */ bool
418
WorkletThread::IsOnWorkletThread()
419
0
{
420
0
  CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();
421
0
  return ccjscx && ccjscx->GetAsWorkletJSContext();
422
0
}
423
424
/* static */ void
425
WorkletThread::AssertIsOnWorkletThread()
426
0
{
427
0
  MOZ_ASSERT(IsOnWorkletThread());
428
0
}
429
430
/* static */ WorkletThread*
431
WorkletThread::Get()
432
0
{
433
0
  AssertIsOnWorkletThread();
434
0
435
0
  CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();
436
0
  MOZ_ASSERT(ccjscx);
437
0
438
0
  WorkletJSContext* workletjscx = ccjscx->GetAsWorkletJSContext();
439
0
  MOZ_ASSERT(workletjscx);
440
0
  return workletjscx->GetWorkletThread();
441
0
}
442
443
// nsIObserver
444
NS_IMETHODIMP
445
WorkletThread::Observe(nsISupports* aSubject, const char* aTopic,
446
                       const char16_t*)
447
0
{
448
0
  MOZ_ASSERT(strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0);
449
0
450
0
  Terminate();
451
0
  return NS_OK;
452
0
}
453
454
NS_IMPL_ISUPPORTS_INHERITED(WorkletThread, nsThread, nsIObserver)
455
456
} // namespace dom
457
} // namespace mozilla