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 "WorkerRunnable.h"
8 
9 #include "nsGlobalWindow.h"
10 #include "nsIEventTarget.h"
11 #include "nsIGlobalObject.h"
12 #include "nsIRunnable.h"
13 #include "nsThreadUtils.h"
14 
15 #include "mozilla/DebugOnly.h"
16 #include "mozilla/ErrorResult.h"
17 #include "mozilla/dom/ScriptSettings.h"
18 #include "mozilla/Telemetry.h"
19 
20 #include "js/RootingAPI.h"
21 #include "js/Value.h"
22 
23 #include "WorkerPrivate.h"
24 #include "WorkerScope.h"
25 
26 namespace mozilla {
27 namespace dom {
28 
29 namespace {
30 
31 const nsIID kWorkerRunnableIID = {
32     0x320cc0b5,
33     0xef12,
34     0x4084,
35     {0x88, 0x6e, 0xca, 0x6a, 0x81, 0xe4, 0x1d, 0x68}};
36 
37 }  // namespace
38 
39 #ifdef DEBUG
WorkerRunnable(WorkerPrivate * aWorkerPrivate,TargetAndBusyBehavior aBehavior)40 WorkerRunnable::WorkerRunnable(WorkerPrivate* aWorkerPrivate,
41                                TargetAndBusyBehavior aBehavior)
42     : mWorkerPrivate(aWorkerPrivate),
43       mBehavior(aBehavior),
44       mCanceled(0),
45       mCallingCancelWithinRun(false) {
46   MOZ_ASSERT(aWorkerPrivate);
47 }
48 #endif
49 
IsDebuggerRunnable() const50 bool WorkerRunnable::IsDebuggerRunnable() const { return false; }
51 
DefaultGlobalObject() const52 nsIGlobalObject* WorkerRunnable::DefaultGlobalObject() const {
53   if (IsDebuggerRunnable()) {
54     return mWorkerPrivate->DebuggerGlobalScope();
55   } else {
56     return mWorkerPrivate->GlobalScope();
57   }
58 }
59 
PreDispatch(WorkerPrivate * aWorkerPrivate)60 bool WorkerRunnable::PreDispatch(WorkerPrivate* aWorkerPrivate) {
61 #ifdef DEBUG
62   MOZ_ASSERT(aWorkerPrivate);
63 
64   switch (mBehavior) {
65     case ParentThreadUnchangedBusyCount:
66       aWorkerPrivate->AssertIsOnWorkerThread();
67       break;
68 
69     case WorkerThreadModifyBusyCount:
70     case WorkerThreadUnchangedBusyCount:
71       aWorkerPrivate->AssertIsOnParentThread();
72       break;
73 
74     default:
75       MOZ_ASSERT_UNREACHABLE("Unknown behavior!");
76   }
77 #endif
78 
79   if (mBehavior == WorkerThreadModifyBusyCount) {
80     return aWorkerPrivate->ModifyBusyCount(true);
81   }
82 
83   return true;
84 }
85 
Dispatch()86 bool WorkerRunnable::Dispatch() {
87   bool ok = PreDispatch(mWorkerPrivate);
88   if (ok) {
89     ok = DispatchInternal();
90   }
91   PostDispatch(mWorkerPrivate, ok);
92   return ok;
93 }
94 
DispatchInternal()95 bool WorkerRunnable::DispatchInternal() {
96   RefPtr<WorkerRunnable> runnable(this);
97 
98   if (mBehavior == WorkerThreadModifyBusyCount ||
99       mBehavior == WorkerThreadUnchangedBusyCount) {
100     if (IsDebuggerRunnable()) {
101       return NS_SUCCEEDED(
102           mWorkerPrivate->DispatchDebuggerRunnable(runnable.forget()));
103     } else {
104       return NS_SUCCEEDED(mWorkerPrivate->Dispatch(runnable.forget()));
105     }
106   }
107 
108   MOZ_ASSERT(mBehavior == ParentThreadUnchangedBusyCount);
109 
110   if (WorkerPrivate* parent = mWorkerPrivate->GetParent()) {
111     return NS_SUCCEEDED(parent->Dispatch(runnable.forget()));
112   }
113 
114   return NS_SUCCEEDED(mWorkerPrivate->DispatchToMainThread(runnable.forget()));
115 }
116 
PostDispatch(WorkerPrivate * aWorkerPrivate,bool aDispatchResult)117 void WorkerRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
118                                   bool aDispatchResult) {
119   MOZ_ASSERT(aWorkerPrivate);
120 
121 #ifdef DEBUG
122   switch (mBehavior) {
123     case ParentThreadUnchangedBusyCount:
124       aWorkerPrivate->AssertIsOnWorkerThread();
125       break;
126 
127     case WorkerThreadModifyBusyCount:
128       aWorkerPrivate->AssertIsOnParentThread();
129       break;
130 
131     case WorkerThreadUnchangedBusyCount:
132       aWorkerPrivate->AssertIsOnParentThread();
133       break;
134 
135     default:
136       MOZ_ASSERT_UNREACHABLE("Unknown behavior!");
137   }
138 #endif
139 
140   if (!aDispatchResult) {
141     if (mBehavior == WorkerThreadModifyBusyCount) {
142       aWorkerPrivate->ModifyBusyCount(false);
143     }
144   }
145 }
146 
PreRun(WorkerPrivate * aWorkerPrivate)147 bool WorkerRunnable::PreRun(WorkerPrivate* aWorkerPrivate) { return true; }
148 
PostRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate,bool aRunResult)149 void WorkerRunnable::PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
150                              bool aRunResult) {
151   MOZ_ASSERT(aCx);
152   MOZ_ASSERT(aWorkerPrivate);
153 
154 #ifdef DEBUG
155   switch (mBehavior) {
156     case ParentThreadUnchangedBusyCount:
157       aWorkerPrivate->AssertIsOnParentThread();
158       break;
159 
160     case WorkerThreadModifyBusyCount:
161       aWorkerPrivate->AssertIsOnWorkerThread();
162       break;
163 
164     case WorkerThreadUnchangedBusyCount:
165       aWorkerPrivate->AssertIsOnWorkerThread();
166       break;
167 
168     default:
169       MOZ_ASSERT_UNREACHABLE("Unknown behavior!");
170   }
171 #endif
172 
173   if (mBehavior == WorkerThreadModifyBusyCount) {
174     aWorkerPrivate->ModifyBusyCountFromWorker(false);
175   }
176 }
177 
178 // static
FromRunnable(nsIRunnable * aRunnable)179 WorkerRunnable* WorkerRunnable::FromRunnable(nsIRunnable* aRunnable) {
180   MOZ_ASSERT(aRunnable);
181 
182   WorkerRunnable* runnable;
183   nsresult rv = aRunnable->QueryInterface(kWorkerRunnableIID,
184                                           reinterpret_cast<void**>(&runnable));
185   if (NS_FAILED(rv)) {
186     return nullptr;
187   }
188 
189   MOZ_ASSERT(runnable);
190   return runnable;
191 }
192 
193 NS_IMPL_ADDREF(WorkerRunnable)
194 NS_IMPL_RELEASE(WorkerRunnable)
195 
196 NS_INTERFACE_MAP_BEGIN(WorkerRunnable)
197   NS_INTERFACE_MAP_ENTRY(nsIRunnable)
198   NS_INTERFACE_MAP_ENTRY(nsICancelableRunnable)
199   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRunnable)
200   // kWorkerRunnableIID is special in that it does not AddRef its result.
201   if (aIID.Equals(kWorkerRunnableIID)) {
202     *aInstancePtr = this;
203     return NS_OK;
204   } else
205 NS_INTERFACE_MAP_END
206 
207 NS_IMETHODIMP
Run()208 WorkerRunnable::Run() {
209   bool targetIsWorkerThread = mBehavior == WorkerThreadModifyBusyCount ||
210                               mBehavior == WorkerThreadUnchangedBusyCount;
211 
212 #ifdef DEBUG
213   MOZ_ASSERT_IF(mCallingCancelWithinRun, targetIsWorkerThread);
214   if (targetIsWorkerThread) {
215     mWorkerPrivate->AssertIsOnWorkerThread();
216   } else {
217     MOZ_ASSERT(mBehavior == ParentThreadUnchangedBusyCount);
218     mWorkerPrivate->AssertIsOnParentThread();
219   }
220 #endif
221 
222   if (IsCanceled() && !mCallingCancelWithinRun) {
223     return NS_OK;
224   }
225 
226   if (targetIsWorkerThread &&
227       mWorkerPrivate->AllPendingRunnablesShouldBeCanceled() && !IsCanceled() &&
228       !mCallingCancelWithinRun) {
229     // Prevent recursion.
230     mCallingCancelWithinRun = true;
231 
232     Cancel();
233 
234     MOZ_ASSERT(mCallingCancelWithinRun);
235     mCallingCancelWithinRun = false;
236 
237     MOZ_ASSERT(IsCanceled(), "Subclass Cancel() didn't set IsCanceled()!");
238 
239     if (mBehavior == WorkerThreadModifyBusyCount) {
240       mWorkerPrivate->ModifyBusyCountFromWorker(false);
241     }
242 
243     return NS_OK;
244   }
245 
246   bool result = PreRun(mWorkerPrivate);
247   if (!result) {
248     MOZ_ASSERT(targetIsWorkerThread,
249                "The only PreRun implementation that can fail is "
250                "ScriptExecutorRunnable");
251     mWorkerPrivate->AssertIsOnWorkerThread();
252     MOZ_ASSERT(!JS_IsExceptionPending(mWorkerPrivate->GetJSContext()));
253     // We can't enter a useful compartment on the JSContext here; just pass it
254     // in as-is.
255     PostRun(mWorkerPrivate->GetJSContext(), mWorkerPrivate, false);
256     return NS_ERROR_FAILURE;
257   }
258 
259   // Track down the appropriate global, if any, to use for the AutoEntryScript.
260   nsCOMPtr<nsIGlobalObject> globalObject;
261   bool isMainThread = !targetIsWorkerThread && !mWorkerPrivate->GetParent();
262   MOZ_ASSERT(isMainThread == NS_IsMainThread());
263   RefPtr<WorkerPrivate> kungFuDeathGrip;
264   if (targetIsWorkerThread) {
265     JSContext* cx = GetCurrentWorkerThreadJSContext();
266     if (NS_WARN_IF(!cx)) {
267       return NS_ERROR_FAILURE;
268     }
269 
270     JSObject* global = JS::CurrentGlobalOrNull(cx);
271     if (global) {
272       globalObject = xpc::NativeGlobal(global);
273     } else {
274       globalObject = DefaultGlobalObject();
275     }
276 
277     // We may still not have a globalObject here: in the case of
278     // CompileScriptRunnable, we don't actually create the global object until
279     // we have the script data, which happens in a syncloop under
280     // CompileScriptRunnable::WorkerRun, so we can't assert that it got created
281     // in the PreRun call above.
282   } else {
283     kungFuDeathGrip = mWorkerPrivate;
284     if (isMainThread) {
285       globalObject = nsGlobalWindowInner::Cast(mWorkerPrivate->GetWindow());
286     } else {
287       globalObject = mWorkerPrivate->GetParent()->GlobalScope();
288     }
289   }
290 
291   // We might run script as part of WorkerRun, so we need an AutoEntryScript.
292   // This is part of the HTML spec for workers at:
293   // http://www.whatwg.org/specs/web-apps/current-work/#run-a-worker
294   // If we don't have a globalObject we have to use an AutoJSAPI instead, but
295   // this is OK as we won't be running script in these circumstances.
296   Maybe<mozilla::dom::AutoJSAPI> maybeJSAPI;
297   Maybe<mozilla::dom::AutoEntryScript> aes;
298   JSContext* cx;
299   AutoJSAPI* jsapi;
300   if (globalObject) {
301     aes.emplace(globalObject, "Worker runnable", isMainThread);
302     jsapi = aes.ptr();
303     cx = aes->cx();
304   } else {
305     maybeJSAPI.emplace();
306     maybeJSAPI->Init();
307     jsapi = maybeJSAPI.ptr();
308     cx = jsapi->cx();
309   }
310 
311   // Note that we can't assert anything about
312   // mWorkerPrivate->ParentEventTargetRef()->GetWrapper()
313   // existing, since it may in fact have been GCed (and we may be one of the
314   // runnables cleaning up the worker as a result).
315 
316   // If we are on the parent thread and that thread is not the main thread,
317   // then we must be a dedicated worker (because there are no
318   // Shared/ServiceWorkers whose parent is itself a worker) and then we
319   // definitely have a globalObject.  If it _is_ the main thread, globalObject
320   // can be null for workers started from JSMs or other non-window contexts,
321   // sadly.
322   MOZ_ASSERT_IF(!targetIsWorkerThread && !isMainThread,
323                 mWorkerPrivate->IsDedicatedWorker() && globalObject);
324 
325   // If we're on the parent thread we might be in a null compartment in the
326   // situation described above when globalObject is null.  Make sure to enter
327   // the compartment of the worker's reflector if there is one.  There might
328   // not be one if we're just starting to compile the script for this worker.
329   Maybe<JSAutoCompartment> ac;
330   if (!targetIsWorkerThread && mWorkerPrivate->IsDedicatedWorker() &&
331       mWorkerPrivate->ParentEventTargetRef()->GetWrapper()) {
332     JSObject* wrapper = mWorkerPrivate->ParentEventTargetRef()->GetWrapper();
333 
334     // If we're on the parent thread and have a reflector and a globalObject,
335     // then the compartments of cx, globalObject, and the worker's reflector
336     // should all match.
337     MOZ_ASSERT_IF(globalObject, js::GetObjectCompartment(wrapper) ==
338                                     js::GetContextCompartment(cx));
339     MOZ_ASSERT_IF(globalObject, js::GetObjectCompartment(wrapper) ==
340                                     js::GetObjectCompartment(
341                                         globalObject->GetGlobalJSObject()));
342 
343     // If we're on the parent thread and have a reflector, then our
344     // JSContext had better be either in the null compartment (and hence
345     // have no globalObject) or in the compartment of our reflector.
346     MOZ_ASSERT(
347         !js::GetContextCompartment(cx) ||
348             js::GetObjectCompartment(wrapper) == js::GetContextCompartment(cx),
349         "Must either be in the null compartment or in our reflector "
350         "compartment");
351 
352     ac.emplace(cx, wrapper);
353   }
354 
355   MOZ_ASSERT(!jsapi->HasException());
356   result = WorkerRun(cx, mWorkerPrivate);
357   MOZ_ASSERT_IF(result, !jsapi->HasException());
358   jsapi->ReportException();
359 
360   // We can't even assert that this didn't create our global, since in the case
361   // of CompileScriptRunnable it _does_.
362 
363   // It would be nice to avoid passing a JSContext to PostRun, but in the case
364   // of ScriptExecutorRunnable we need to know the current compartment on the
365   // JSContext (the one we set up based on the global returned from PreRun) so
366   // that we can sanely do exception reporting.  In particular, we want to make
367   // sure that we do our JS_SetPendingException while still in that compartment,
368   // because otherwise we might end up trying to create a cross-compartment
369   // wrapper when we try to move the JS exception from our runnable's
370   // ErrorResult to the JSContext, and that's not desirable in this case.
371   //
372   // We _could_ skip passing a JSContext here and then in
373   // ScriptExecutorRunnable::PostRun end up grabbing it from the WorkerPrivate
374   // and looking at its current compartment.  But that seems like slightly weird
375   // action-at-a-distance...
376   //
377   // In any case, we do NOT try to change the compartment on the JSContext at
378   // this point; in the one case in which we could do that
379   // (CompileScriptRunnable) it actually doesn't matter which compartment we're
380   // in for PostRun.
381   PostRun(cx, mWorkerPrivate, result);
382   MOZ_ASSERT(!jsapi->HasException());
383 
384   return result ? NS_OK : NS_ERROR_FAILURE;
385 }
386 
Cancel()387 nsresult WorkerRunnable::Cancel() {
388   uint32_t canceledCount = ++mCanceled;
389 
390   MOZ_ASSERT(canceledCount, "Cancel() overflow!");
391 
392   // The docs say that Cancel() should not be called more than once and that we
393   // should throw NS_ERROR_UNEXPECTED if it is.
394   return (canceledCount == 1) ? NS_OK : NS_ERROR_UNEXPECTED;
395 }
396 
PostDispatch(WorkerPrivate * aWorkerPrivate,bool aDispatchResult)397 void WorkerDebuggerRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
398                                           bool aDispatchResult) {}
399 
WorkerSyncRunnable(WorkerPrivate * aWorkerPrivate,nsIEventTarget * aSyncLoopTarget)400 WorkerSyncRunnable::WorkerSyncRunnable(WorkerPrivate* aWorkerPrivate,
401                                        nsIEventTarget* aSyncLoopTarget)
402     : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
403       mSyncLoopTarget(aSyncLoopTarget) {
404 #ifdef DEBUG
405   if (mSyncLoopTarget) {
406     mWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget);
407   }
408 #endif
409 }
410 
WorkerSyncRunnable(WorkerPrivate * aWorkerPrivate,already_AddRefed<nsIEventTarget> && aSyncLoopTarget)411 WorkerSyncRunnable::WorkerSyncRunnable(
412     WorkerPrivate* aWorkerPrivate,
413     already_AddRefed<nsIEventTarget>&& aSyncLoopTarget)
414     : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
415       mSyncLoopTarget(aSyncLoopTarget) {
416 #ifdef DEBUG
417   if (mSyncLoopTarget) {
418     mWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget);
419   }
420 #endif
421 }
422 
~WorkerSyncRunnable()423 WorkerSyncRunnable::~WorkerSyncRunnable() {}
424 
DispatchInternal()425 bool WorkerSyncRunnable::DispatchInternal() {
426   if (mSyncLoopTarget) {
427     RefPtr<WorkerSyncRunnable> runnable(this);
428     return NS_SUCCEEDED(
429         mSyncLoopTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL));
430   }
431 
432   return WorkerRunnable::DispatchInternal();
433 }
434 
PostDispatch(WorkerPrivate * aWorkerPrivate,bool aDispatchResult)435 void MainThreadWorkerSyncRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
436                                                 bool aDispatchResult) {}
437 
MainThreadStopSyncLoopRunnable(WorkerPrivate * aWorkerPrivate,already_AddRefed<nsIEventTarget> && aSyncLoopTarget,bool aResult)438 MainThreadStopSyncLoopRunnable::MainThreadStopSyncLoopRunnable(
439     WorkerPrivate* aWorkerPrivate,
440     already_AddRefed<nsIEventTarget>&& aSyncLoopTarget, bool aResult)
441     : WorkerSyncRunnable(aWorkerPrivate, Move(aSyncLoopTarget)),
442       mResult(aResult) {
443   AssertIsOnMainThread();
444 #ifdef DEBUG
445   mWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget);
446 #endif
447 }
448 
Cancel()449 nsresult MainThreadStopSyncLoopRunnable::Cancel() {
450   nsresult rv = Run();
451   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Run() failed");
452 
453   nsresult rv2 = WorkerSyncRunnable::Cancel();
454   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv2), "Cancel() failed");
455 
456   return NS_FAILED(rv) ? rv : rv2;
457 }
458 
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)459 bool MainThreadStopSyncLoopRunnable::WorkerRun(JSContext* aCx,
460                                                WorkerPrivate* aWorkerPrivate) {
461   aWorkerPrivate->AssertIsOnWorkerThread();
462   MOZ_ASSERT(mSyncLoopTarget);
463 
464   nsCOMPtr<nsIEventTarget> syncLoopTarget;
465   mSyncLoopTarget.swap(syncLoopTarget);
466 
467   aWorkerPrivate->StopSyncLoop(syncLoopTarget, mResult);
468   return true;
469 }
470 
DispatchInternal()471 bool MainThreadStopSyncLoopRunnable::DispatchInternal() {
472   MOZ_ASSERT(mSyncLoopTarget);
473 
474   RefPtr<MainThreadStopSyncLoopRunnable> runnable(this);
475   return NS_SUCCEEDED(
476       mSyncLoopTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL));
477 }
478 
PostDispatch(WorkerPrivate * aWorkerPrivate,bool aDispatchResult)479 void MainThreadStopSyncLoopRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
480                                                   bool aDispatchResult) {}
481 
482 #ifdef DEBUG
WorkerControlRunnable(WorkerPrivate * aWorkerPrivate,TargetAndBusyBehavior aBehavior)483 WorkerControlRunnable::WorkerControlRunnable(WorkerPrivate* aWorkerPrivate,
484                                              TargetAndBusyBehavior aBehavior)
485     : WorkerRunnable(aWorkerPrivate, aBehavior) {
486   MOZ_ASSERT(aWorkerPrivate);
487   MOZ_ASSERT(aBehavior == ParentThreadUnchangedBusyCount ||
488                  aBehavior == WorkerThreadUnchangedBusyCount,
489              "WorkerControlRunnables should not modify the busy count");
490 }
491 #endif
492 
Cancel()493 nsresult WorkerControlRunnable::Cancel() {
494   if (NS_FAILED(Run())) {
495     NS_WARNING("WorkerControlRunnable::Run() failed.");
496   }
497 
498   return WorkerRunnable::Cancel();
499 }
500 
DispatchInternal()501 bool WorkerControlRunnable::DispatchInternal() {
502   RefPtr<WorkerControlRunnable> runnable(this);
503 
504   if (mBehavior == WorkerThreadUnchangedBusyCount) {
505     return NS_SUCCEEDED(
506         mWorkerPrivate->DispatchControlRunnable(runnable.forget()));
507   }
508 
509   if (WorkerPrivate* parent = mWorkerPrivate->GetParent()) {
510     return NS_SUCCEEDED(parent->DispatchControlRunnable(runnable.forget()));
511   }
512 
513   return NS_SUCCEEDED(mWorkerPrivate->DispatchToMainThread(runnable.forget()));
514 }
515 
WorkerMainThreadRunnable(WorkerPrivate * aWorkerPrivate,const nsACString & aTelemetryKey)516 WorkerMainThreadRunnable::WorkerMainThreadRunnable(
517     WorkerPrivate* aWorkerPrivate, const nsACString& aTelemetryKey)
518     : mozilla::Runnable("dom::WorkerMainThreadRunnable"),
519       mWorkerPrivate(aWorkerPrivate),
520       mTelemetryKey(aTelemetryKey) {
521   mWorkerPrivate->AssertIsOnWorkerThread();
522 }
523 
Dispatch(WorkerStatus aFailStatus,mozilla::ErrorResult & aRv)524 void WorkerMainThreadRunnable::Dispatch(WorkerStatus aFailStatus,
525                                         mozilla::ErrorResult& aRv) {
526   mWorkerPrivate->AssertIsOnWorkerThread();
527 
528   TimeStamp startTime = TimeStamp::NowLoRes();
529 
530   AutoSyncLoopHolder syncLoop(mWorkerPrivate, aFailStatus);
531 
532   mSyncLoopTarget = syncLoop.GetEventTarget();
533   if (!mSyncLoopTarget) {
534     // SyncLoop creation can fail if the worker is shutting down.
535     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
536     return;
537   }
538 
539   DebugOnly<nsresult> rv = mWorkerPrivate->DispatchToMainThread(this);
540   MOZ_ASSERT(
541       NS_SUCCEEDED(rv),
542       "Should only fail after xpcom-shutdown-threads and we're gone by then");
543 
544   bool success = syncLoop.Run();
545 
546   Telemetry::Accumulate(
547       Telemetry::SYNC_WORKER_OPERATION, mTelemetryKey,
548       static_cast<uint32_t>(
549           (TimeStamp::NowLoRes() - startTime).ToMilliseconds()));
550 
551   Unused << startTime;  // Shut the compiler up.
552 
553   if (!success) {
554     aRv.ThrowUncatchableException();
555   }
556 }
557 
558 NS_IMETHODIMP
Run()559 WorkerMainThreadRunnable::Run() {
560   AssertIsOnMainThread();
561 
562   bool runResult = MainThreadRun();
563 
564   RefPtr<MainThreadStopSyncLoopRunnable> response =
565       new MainThreadStopSyncLoopRunnable(mWorkerPrivate,
566                                          mSyncLoopTarget.forget(), runResult);
567 
568   MOZ_ALWAYS_TRUE(response->Dispatch());
569 
570   return NS_OK;
571 }
572 
PreDispatch(WorkerPrivate * aWorkerPrivate)573 bool WorkerSameThreadRunnable::PreDispatch(WorkerPrivate* aWorkerPrivate) {
574   // We don't call WorkerRunnable::PreDispatch, because we're using
575   // WorkerThreadModifyBusyCount for mBehavior, and WorkerRunnable will assert
576   // that PreDispatch is on the parent thread in that case.
577   aWorkerPrivate->AssertIsOnWorkerThread();
578   return true;
579 }
580 
PostDispatch(WorkerPrivate * aWorkerPrivate,bool aDispatchResult)581 void WorkerSameThreadRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
582                                             bool aDispatchResult) {
583   // We don't call WorkerRunnable::PostDispatch, because we're using
584   // WorkerThreadModifyBusyCount for mBehavior, and WorkerRunnable will assert
585   // that PostDispatch is on the parent thread in that case.
586   aWorkerPrivate->AssertIsOnWorkerThread();
587   if (aDispatchResult) {
588     DebugOnly<bool> willIncrement =
589         aWorkerPrivate->ModifyBusyCountFromWorker(true);
590     // Should never fail since if this thread is still running, so should the
591     // parent and it should be able to process a control runnable.
592     MOZ_ASSERT(willIncrement);
593   }
594 }
595 
WorkerProxyToMainThreadRunnable(WorkerPrivate * aWorkerPrivate)596 WorkerProxyToMainThreadRunnable::WorkerProxyToMainThreadRunnable(
597     WorkerPrivate* aWorkerPrivate)
598     : mozilla::Runnable("dom::WorkerProxyToMainThreadRunnable"),
599       mWorkerPrivate(aWorkerPrivate) {
600   MOZ_ASSERT(mWorkerPrivate);
601   mWorkerPrivate->AssertIsOnWorkerThread();
602 }
603 
~WorkerProxyToMainThreadRunnable()604 WorkerProxyToMainThreadRunnable::~WorkerProxyToMainThreadRunnable() {}
605 
Dispatch()606 bool WorkerProxyToMainThreadRunnable::Dispatch() {
607   mWorkerPrivate->AssertIsOnWorkerThread();
608 
609   if (NS_WARN_IF(!HoldWorker())) {
610     RunBackOnWorkerThreadForCleanup();
611     return false;
612   }
613 
614   if (NS_WARN_IF(NS_FAILED(mWorkerPrivate->DispatchToMainThread(this)))) {
615     ReleaseWorker();
616     RunBackOnWorkerThreadForCleanup();
617     return false;
618   }
619 
620   return true;
621 }
622 
623 NS_IMETHODIMP
Run()624 WorkerProxyToMainThreadRunnable::Run() {
625   AssertIsOnMainThread();
626   RunOnMainThread();
627   PostDispatchOnMainThread();
628   return NS_OK;
629 }
630 
PostDispatchOnMainThread()631 void WorkerProxyToMainThreadRunnable::PostDispatchOnMainThread() {
632   class ReleaseRunnable final : public MainThreadWorkerControlRunnable {
633     RefPtr<WorkerProxyToMainThreadRunnable> mRunnable;
634 
635    public:
636     ReleaseRunnable(WorkerPrivate* aWorkerPrivate,
637                     WorkerProxyToMainThreadRunnable* aRunnable)
638         : MainThreadWorkerControlRunnable(aWorkerPrivate),
639           mRunnable(aRunnable) {
640       MOZ_ASSERT(aRunnable);
641     }
642 
643     // We must call RunBackOnWorkerThreadForCleanup() also if the runnable is
644     // canceled.
645     nsresult Cancel() override {
646       WorkerRun(nullptr, mWorkerPrivate);
647       return MainThreadWorkerControlRunnable::Cancel();
648     }
649 
650     virtual bool WorkerRun(JSContext* aCx,
651                            WorkerPrivate* aWorkerPrivate) override {
652       MOZ_ASSERT(aWorkerPrivate);
653       aWorkerPrivate->AssertIsOnWorkerThread();
654 
655       if (mRunnable) {
656         mRunnable->RunBackOnWorkerThreadForCleanup();
657 
658         // Let's release the worker thread.
659         mRunnable->ReleaseWorker();
660         mRunnable = nullptr;
661       }
662 
663       return true;
664     }
665 
666    private:
667     ~ReleaseRunnable() {}
668   };
669 
670   RefPtr<WorkerControlRunnable> runnable =
671       new ReleaseRunnable(mWorkerPrivate, this);
672   Unused << NS_WARN_IF(!runnable->Dispatch());
673 }
674 
HoldWorker()675 bool WorkerProxyToMainThreadRunnable::HoldWorker() {
676   mWorkerPrivate->AssertIsOnWorkerThread();
677   MOZ_ASSERT(!mWorkerHolder);
678 
679   class SimpleWorkerHolder final : public WorkerHolder {
680    public:
681     SimpleWorkerHolder()
682         : WorkerHolder("WorkerProxyToMainThreadRunnable::SimpleWorkerHolder") {}
683 
684     bool Notify(WorkerStatus aStatus) override {
685       // We don't care about the notification. We just want to keep the
686       // mWorkerPrivate alive.
687       return true;
688     }
689   };
690 
691   UniquePtr<WorkerHolder> workerHolder(new SimpleWorkerHolder());
692   if (NS_WARN_IF(!workerHolder->HoldWorker(mWorkerPrivate, Canceling))) {
693     return false;
694   }
695 
696   mWorkerHolder = Move(workerHolder);
697   return true;
698 }
699 
ReleaseWorker()700 void WorkerProxyToMainThreadRunnable::ReleaseWorker() {
701   mWorkerPrivate->AssertIsOnWorkerThread();
702   MOZ_ASSERT(mWorkerHolder);
703   mWorkerHolder = nullptr;
704 }
705 
706 }  // namespace dom
707 }  // namespace mozilla
708