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