Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "WorkerRunnable.h"
8 #include "nsIEventTarget.h"
9 #include "nsIRunnable.h"
10 #include "nsThreadUtils.h"
12 #include "mozilla/DebugOnly.h"
14 #include "js/RootingAPI.h"
15 #include "js/Value.h"
17 #include "WorkerPrivate.h"
19 USING_WORKERS_NAMESPACE
21 namespace {
23 const nsIID kWorkerRunnableIID = {
24 0x320cc0b5, 0xef12, 0x4084, { 0x88, 0x6e, 0xca, 0x6a, 0x81, 0xe4, 0x1d, 0x68 }
25 };
27 void
28 MaybeReportMainThreadException(JSContext* aCx, bool aResult)
29 {
30 AssertIsOnMainThread();
32 if (aCx && !aResult) {
33 JS_ReportPendingException(aCx);
34 }
35 }
37 } // anonymous namespace
39 #ifdef DEBUG
40 WorkerRunnable::WorkerRunnable(WorkerPrivate* aWorkerPrivate,
41 TargetAndBusyBehavior aBehavior)
42 : mWorkerPrivate(aWorkerPrivate), mBehavior(aBehavior), mCanceled(0),
43 mCallingCancelWithinRun(false)
44 {
45 MOZ_ASSERT(aWorkerPrivate);
46 }
47 #endif
49 bool
50 WorkerRunnable::PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
51 {
52 #ifdef DEBUG
53 MOZ_ASSERT(aWorkerPrivate);
55 switch (mBehavior) {
56 case ParentThreadUnchangedBusyCount:
57 aWorkerPrivate->AssertIsOnWorkerThread();
58 break;
60 case WorkerThreadModifyBusyCount:
61 aWorkerPrivate->AssertIsOnParentThread();
62 MOZ_ASSERT(aCx);
63 break;
65 case WorkerThreadUnchangedBusyCount:
66 aWorkerPrivate->AssertIsOnParentThread();
67 break;
69 default:
70 MOZ_ASSUME_UNREACHABLE("Unknown behavior!");
71 }
72 #endif
74 if (mBehavior == WorkerThreadModifyBusyCount) {
75 return aWorkerPrivate->ModifyBusyCount(aCx, true);
76 }
78 return true;
79 }
81 bool
82 WorkerRunnable::Dispatch(JSContext* aCx)
83 {
84 bool ok;
86 if (!aCx) {
87 ok = PreDispatch(nullptr, mWorkerPrivate);
88 if (ok) {
89 ok = DispatchInternal();
90 }
91 PostDispatch(nullptr, mWorkerPrivate, ok);
92 return ok;
93 }
95 JSAutoRequest ar(aCx);
97 JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
99 Maybe<JSAutoCompartment> ac;
100 if (global) {
101 ac.construct(aCx, global);
102 }
104 ok = PreDispatch(aCx, mWorkerPrivate);
106 if (ok && !DispatchInternal()) {
107 ok = false;
108 }
110 PostDispatch(aCx, mWorkerPrivate, ok);
112 return ok;
113 }
115 bool
116 WorkerRunnable::DispatchInternal()
117 {
118 if (mBehavior == WorkerThreadModifyBusyCount ||
119 mBehavior == WorkerThreadUnchangedBusyCount) {
120 return NS_SUCCEEDED(mWorkerPrivate->Dispatch(this));
121 }
123 MOZ_ASSERT(mBehavior == ParentThreadUnchangedBusyCount);
125 if (WorkerPrivate* parent = mWorkerPrivate->GetParent()) {
126 return NS_SUCCEEDED(parent->Dispatch(this));
127 }
129 nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
130 MOZ_ASSERT(mainThread);
132 return NS_SUCCEEDED(mainThread->Dispatch(this, NS_DISPATCH_NORMAL));
133 }
135 void
136 WorkerRunnable::PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
137 bool aDispatchResult)
138 {
139 MOZ_ASSERT(aWorkerPrivate);
141 #ifdef DEBUG
142 switch (mBehavior) {
143 case ParentThreadUnchangedBusyCount:
144 aWorkerPrivate->AssertIsOnWorkerThread();
145 break;
147 case WorkerThreadModifyBusyCount:
148 aWorkerPrivate->AssertIsOnParentThread();
149 MOZ_ASSERT(aCx);
150 break;
152 case WorkerThreadUnchangedBusyCount:
153 aWorkerPrivate->AssertIsOnParentThread();
154 break;
156 default:
157 MOZ_ASSUME_UNREACHABLE("Unknown behavior!");
158 }
159 #endif
161 if (!aDispatchResult) {
162 if (mBehavior == WorkerThreadModifyBusyCount) {
163 aWorkerPrivate->ModifyBusyCount(aCx, false);
164 }
165 if (aCx) {
166 JS_ReportPendingException(aCx);
167 }
168 }
169 }
171 void
172 WorkerRunnable::PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
173 bool aRunResult)
174 {
175 MOZ_ASSERT(aCx);
176 MOZ_ASSERT(aWorkerPrivate);
178 #ifdef DEBUG
179 switch (mBehavior) {
180 case ParentThreadUnchangedBusyCount:
181 aWorkerPrivate->AssertIsOnParentThread();
182 break;
184 case WorkerThreadModifyBusyCount:
185 aWorkerPrivate->AssertIsOnWorkerThread();
186 break;
188 case WorkerThreadUnchangedBusyCount:
189 aWorkerPrivate->AssertIsOnWorkerThread();
190 break;
192 default:
193 MOZ_ASSUME_UNREACHABLE("Unknown behavior!");
194 }
195 #endif
197 if (mBehavior == WorkerThreadModifyBusyCount) {
198 if (!aWorkerPrivate->ModifyBusyCountFromWorker(aCx, false)) {
199 aRunResult = false;
200 }
201 }
203 if (!aRunResult) {
204 JS_ReportPendingException(aCx);
205 }
206 }
208 // static
209 WorkerRunnable*
210 WorkerRunnable::FromRunnable(nsIRunnable* aRunnable)
211 {
212 MOZ_ASSERT(aRunnable);
214 WorkerRunnable* runnable;
215 nsresult rv = aRunnable->QueryInterface(kWorkerRunnableIID,
216 reinterpret_cast<void**>(&runnable));
217 if (NS_FAILED(rv)) {
218 return nullptr;
219 }
221 MOZ_ASSERT(runnable);
222 return runnable;
223 }
225 NS_IMPL_ADDREF(WorkerRunnable)
226 NS_IMPL_RELEASE(WorkerRunnable)
228 NS_INTERFACE_MAP_BEGIN(WorkerRunnable)
229 NS_INTERFACE_MAP_ENTRY(nsIRunnable)
230 NS_INTERFACE_MAP_ENTRY(nsICancelableRunnable)
231 NS_INTERFACE_MAP_ENTRY(nsISupports)
232 // kWorkerRunnableIID is special in that it does not AddRef its result.
233 if (aIID.Equals(kWorkerRunnableIID)) {
234 *aInstancePtr = this;
235 return NS_OK;
236 }
237 else
238 NS_INTERFACE_MAP_END
240 NS_IMETHODIMP
241 WorkerRunnable::Run()
242 {
243 bool targetIsWorkerThread = mBehavior == WorkerThreadModifyBusyCount ||
244 mBehavior == WorkerThreadUnchangedBusyCount;
246 #ifdef DEBUG
247 MOZ_ASSERT_IF(mCallingCancelWithinRun, targetIsWorkerThread);
248 if (targetIsWorkerThread) {
249 mWorkerPrivate->AssertIsOnWorkerThread();
250 }
251 else {
252 MOZ_ASSERT(mBehavior == ParentThreadUnchangedBusyCount);
253 mWorkerPrivate->AssertIsOnParentThread();
254 }
255 #endif
257 if (IsCanceled() && !mCallingCancelWithinRun) {
258 return NS_OK;
259 }
261 JSContext* cx;
262 nsRefPtr<WorkerPrivate> kungFuDeathGrip;
263 nsCxPusher pusher;
265 if (targetIsWorkerThread) {
266 if (mWorkerPrivate->AllPendingRunnablesShouldBeCanceled() &&
267 !IsCanceled() &&
268 !mCallingCancelWithinRun) {
270 // Prevent recursion.
271 mCallingCancelWithinRun = true;
273 Cancel();
275 MOZ_ASSERT(mCallingCancelWithinRun);
276 mCallingCancelWithinRun = false;
278 MOZ_ASSERT(IsCanceled(), "Subclass Cancel() didn't set IsCanceled()!");
280 return NS_OK;
281 }
283 cx = mWorkerPrivate->GetJSContext();
284 MOZ_ASSERT(cx);
285 }
286 else {
287 cx = mWorkerPrivate->ParentJSContext();
288 MOZ_ASSERT(cx);
290 kungFuDeathGrip = mWorkerPrivate;
292 if (!mWorkerPrivate->GetParent()) {
293 AssertIsOnMainThread();
294 pusher.Push(cx);
295 }
296 }
298 JSAutoRequest ar(cx);
300 JS::Rooted<JSObject*> targetCompartmentObject(cx);
301 if (targetIsWorkerThread) {
302 targetCompartmentObject = JS::CurrentGlobalOrNull(cx);
303 } else {
304 targetCompartmentObject = mWorkerPrivate->GetWrapper();
305 }
307 Maybe<JSAutoCompartment> ac;
308 if (targetCompartmentObject) {
309 ac.construct(cx, targetCompartmentObject);
310 }
312 bool result = WorkerRun(cx, mWorkerPrivate);
314 // In the case of CompileScriptRunnnable, WorkerRun above can cause us to
315 // lazily create a global, in which case we need to be in its compartment
316 // when calling PostRun() below. Maybe<> this time...
317 if (targetIsWorkerThread &&
318 ac.empty() &&
319 js::DefaultObjectForContextOrNull(cx)) {
320 ac.construct(cx, js::DefaultObjectForContextOrNull(cx));
321 }
323 PostRun(cx, mWorkerPrivate, result);
325 return result ? NS_OK : NS_ERROR_FAILURE;
326 }
328 NS_IMETHODIMP
329 WorkerRunnable::Cancel()
330 {
331 uint32_t canceledCount = ++mCanceled;
333 MOZ_ASSERT(canceledCount, "Cancel() overflow!");
335 // The docs say that Cancel() should not be called more than once and that we
336 // should throw NS_ERROR_UNEXPECTED if it is.
337 return (canceledCount == 1) ? NS_OK : NS_ERROR_UNEXPECTED;
338 }
340 WorkerSyncRunnable::WorkerSyncRunnable(WorkerPrivate* aWorkerPrivate,
341 nsIEventTarget* aSyncLoopTarget)
342 : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
343 mSyncLoopTarget(aSyncLoopTarget)
344 {
345 #ifdef DEBUG
346 if (mSyncLoopTarget) {
347 mWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget);
348 }
349 #endif
350 }
352 WorkerSyncRunnable::WorkerSyncRunnable(
353 WorkerPrivate* aWorkerPrivate,
354 already_AddRefed<nsIEventTarget>&& aSyncLoopTarget)
355 : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
356 mSyncLoopTarget(aSyncLoopTarget)
357 {
358 #ifdef DEBUG
359 if (mSyncLoopTarget) {
360 mWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget);
361 }
362 #endif
363 }
365 WorkerSyncRunnable::~WorkerSyncRunnable()
366 {
367 }
369 bool
370 WorkerSyncRunnable::DispatchInternal()
371 {
372 if (mSyncLoopTarget) {
373 return NS_SUCCEEDED(mSyncLoopTarget->Dispatch(this, NS_DISPATCH_NORMAL));
374 }
376 return WorkerRunnable::DispatchInternal();
377 }
379 void
380 MainThreadWorkerSyncRunnable::PostDispatch(JSContext* aCx,
381 WorkerPrivate* aWorkerPrivate,
382 bool aDispatchResult)
383 {
384 MaybeReportMainThreadException(aCx, aDispatchResult);
385 }
387 StopSyncLoopRunnable::StopSyncLoopRunnable(
388 WorkerPrivate* aWorkerPrivate,
389 already_AddRefed<nsIEventTarget>&& aSyncLoopTarget,
390 bool aResult)
391 : WorkerSyncRunnable(aWorkerPrivate, Move(aSyncLoopTarget)), mResult(aResult)
392 {
393 #ifdef DEBUG
394 mWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget);
395 #endif
396 }
398 NS_IMETHODIMP
399 StopSyncLoopRunnable::Cancel()
400 {
401 nsresult rv = Run();
402 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Run() failed");
404 nsresult rv2 = WorkerSyncRunnable::Cancel();
405 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv2), "Cancel() failed");
407 return NS_FAILED(rv) ? rv : rv2;
408 }
410 bool
411 StopSyncLoopRunnable::WorkerRun(JSContext* aCx,
412 WorkerPrivate* aWorkerPrivate)
413 {
414 aWorkerPrivate->AssertIsOnWorkerThread();
415 MOZ_ASSERT(mSyncLoopTarget);
417 nsCOMPtr<nsIEventTarget> syncLoopTarget;
418 mSyncLoopTarget.swap(syncLoopTarget);
420 if (!mResult) {
421 MaybeSetException(aCx);
422 }
424 aWorkerPrivate->StopSyncLoop(syncLoopTarget, mResult);
425 return true;
426 }
428 bool
429 StopSyncLoopRunnable::DispatchInternal()
430 {
431 MOZ_ASSERT(mSyncLoopTarget);
433 return NS_SUCCEEDED(mSyncLoopTarget->Dispatch(this, NS_DISPATCH_NORMAL));
434 }
436 void
437 MainThreadStopSyncLoopRunnable::PostDispatch(JSContext* aCx,
438 WorkerPrivate* aWorkerPrivate,
439 bool aDispatchResult)
440 {
441 MaybeReportMainThreadException(aCx, aDispatchResult);
442 }
444 #ifdef DEBUG
445 WorkerControlRunnable::WorkerControlRunnable(WorkerPrivate* aWorkerPrivate,
446 TargetAndBusyBehavior aBehavior)
447 : WorkerRunnable(aWorkerPrivate, aBehavior)
448 {
449 MOZ_ASSERT(aWorkerPrivate);
450 MOZ_ASSERT(aBehavior == ParentThreadUnchangedBusyCount ||
451 aBehavior == WorkerThreadUnchangedBusyCount,
452 "WorkerControlRunnables should not modify the busy count");
453 }
454 #endif
456 bool
457 WorkerControlRunnable::DispatchInternal()
458 {
459 if (mBehavior == WorkerThreadUnchangedBusyCount) {
460 return NS_SUCCEEDED(mWorkerPrivate->DispatchControlRunnable(this));
461 }
463 if (WorkerPrivate* parent = mWorkerPrivate->GetParent()) {
464 return NS_SUCCEEDED(parent->DispatchControlRunnable(this));
465 }
467 nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
468 MOZ_ASSERT(mainThread);
470 return NS_SUCCEEDED(mainThread->Dispatch(this, NS_DISPATCH_NORMAL));
471 }
473 void
474 MainThreadWorkerControlRunnable::PostDispatch(JSContext* aCx,
475 WorkerPrivate* aWorkerPrivate,
476 bool aDispatchResult)
477 {
478 AssertIsOnMainThread();
480 if (aCx && !aDispatchResult) {
481 JS_ReportPendingException(aCx);
482 }
483 }
485 NS_IMPL_ISUPPORTS_INHERITED0(WorkerControlRunnable, WorkerRunnable)
487 bool
488 WorkerSameThreadRunnable::PreDispatch(JSContext* aCx,
489 WorkerPrivate* aWorkerPrivate)
490 {
491 aWorkerPrivate->AssertIsOnWorkerThread();
492 return true;
493 }
495 void
496 WorkerSameThreadRunnable::PostDispatch(JSContext* aCx,
497 WorkerPrivate* aWorkerPrivate,
498 bool aDispatchResult)
499 {
500 aWorkerPrivate->AssertIsOnWorkerThread();
501 if (aDispatchResult) {
502 DebugOnly<bool> willIncrement = aWorkerPrivate->ModifyBusyCountFromWorker(aCx, true);
503 // Should never fail since if this thread is still running, so should the
504 // parent and it should be able to process a control runnable.
505 MOZ_ASSERT(willIncrement);
506 }
507 }
509 void
510 WorkerSameThreadRunnable::PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
511 bool aRunResult)
512 {
513 MOZ_ASSERT(aCx);
514 MOZ_ASSERT(aWorkerPrivate);
516 aWorkerPrivate->AssertIsOnWorkerThread();
518 DebugOnly<bool> willDecrement = aWorkerPrivate->ModifyBusyCountFromWorker(aCx, false);
519 MOZ_ASSERT(willDecrement);
521 if (!aRunResult) {
522 JS_ReportPendingException(aCx);
523 }
524 }