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