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