dom/workers/WorkerRunnable.cpp

branch
TOR_BUG_9701
changeset 14
925c144e1f1f
equal deleted inserted replaced
-1:000000000000 0:61d1278b5bf6
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/. */
5
6 #include "WorkerRunnable.h"
7
8 #include "nsIEventTarget.h"
9 #include "nsIRunnable.h"
10 #include "nsThreadUtils.h"
11
12 #include "mozilla/DebugOnly.h"
13
14 #include "js/RootingAPI.h"
15 #include "js/Value.h"
16
17 #include "WorkerPrivate.h"
18
19 USING_WORKERS_NAMESPACE
20
21 namespace {
22
23 const nsIID kWorkerRunnableIID = {
24 0x320cc0b5, 0xef12, 0x4084, { 0x88, 0x6e, 0xca, 0x6a, 0x81, 0xe4, 0x1d, 0x68 }
25 };
26
27 void
28 MaybeReportMainThreadException(JSContext* aCx, bool aResult)
29 {
30 AssertIsOnMainThread();
31
32 if (aCx && !aResult) {
33 JS_ReportPendingException(aCx);
34 }
35 }
36
37 } // anonymous namespace
38
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
48
49 bool
50 WorkerRunnable::PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
51 {
52 #ifdef DEBUG
53 MOZ_ASSERT(aWorkerPrivate);
54
55 switch (mBehavior) {
56 case ParentThreadUnchangedBusyCount:
57 aWorkerPrivate->AssertIsOnWorkerThread();
58 break;
59
60 case WorkerThreadModifyBusyCount:
61 aWorkerPrivate->AssertIsOnParentThread();
62 MOZ_ASSERT(aCx);
63 break;
64
65 case WorkerThreadUnchangedBusyCount:
66 aWorkerPrivate->AssertIsOnParentThread();
67 break;
68
69 default:
70 MOZ_ASSUME_UNREACHABLE("Unknown behavior!");
71 }
72 #endif
73
74 if (mBehavior == WorkerThreadModifyBusyCount) {
75 return aWorkerPrivate->ModifyBusyCount(aCx, true);
76 }
77
78 return true;
79 }
80
81 bool
82 WorkerRunnable::Dispatch(JSContext* aCx)
83 {
84 bool ok;
85
86 if (!aCx) {
87 ok = PreDispatch(nullptr, mWorkerPrivate);
88 if (ok) {
89 ok = DispatchInternal();
90 }
91 PostDispatch(nullptr, mWorkerPrivate, ok);
92 return ok;
93 }
94
95 JSAutoRequest ar(aCx);
96
97 JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
98
99 Maybe<JSAutoCompartment> ac;
100 if (global) {
101 ac.construct(aCx, global);
102 }
103
104 ok = PreDispatch(aCx, mWorkerPrivate);
105
106 if (ok && !DispatchInternal()) {
107 ok = false;
108 }
109
110 PostDispatch(aCx, mWorkerPrivate, ok);
111
112 return ok;
113 }
114
115 bool
116 WorkerRunnable::DispatchInternal()
117 {
118 if (mBehavior == WorkerThreadModifyBusyCount ||
119 mBehavior == WorkerThreadUnchangedBusyCount) {
120 return NS_SUCCEEDED(mWorkerPrivate->Dispatch(this));
121 }
122
123 MOZ_ASSERT(mBehavior == ParentThreadUnchangedBusyCount);
124
125 if (WorkerPrivate* parent = mWorkerPrivate->GetParent()) {
126 return NS_SUCCEEDED(parent->Dispatch(this));
127 }
128
129 nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
130 MOZ_ASSERT(mainThread);
131
132 return NS_SUCCEEDED(mainThread->Dispatch(this, NS_DISPATCH_NORMAL));
133 }
134
135 void
136 WorkerRunnable::PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
137 bool aDispatchResult)
138 {
139 MOZ_ASSERT(aWorkerPrivate);
140
141 #ifdef DEBUG
142 switch (mBehavior) {
143 case ParentThreadUnchangedBusyCount:
144 aWorkerPrivate->AssertIsOnWorkerThread();
145 break;
146
147 case WorkerThreadModifyBusyCount:
148 aWorkerPrivate->AssertIsOnParentThread();
149 MOZ_ASSERT(aCx);
150 break;
151
152 case WorkerThreadUnchangedBusyCount:
153 aWorkerPrivate->AssertIsOnParentThread();
154 break;
155
156 default:
157 MOZ_ASSUME_UNREACHABLE("Unknown behavior!");
158 }
159 #endif
160
161 if (!aDispatchResult) {
162 if (mBehavior == WorkerThreadModifyBusyCount) {
163 aWorkerPrivate->ModifyBusyCount(aCx, false);
164 }
165 if (aCx) {
166 JS_ReportPendingException(aCx);
167 }
168 }
169 }
170
171 void
172 WorkerRunnable::PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
173 bool aRunResult)
174 {
175 MOZ_ASSERT(aCx);
176 MOZ_ASSERT(aWorkerPrivate);
177
178 #ifdef DEBUG
179 switch (mBehavior) {
180 case ParentThreadUnchangedBusyCount:
181 aWorkerPrivate->AssertIsOnParentThread();
182 break;
183
184 case WorkerThreadModifyBusyCount:
185 aWorkerPrivate->AssertIsOnWorkerThread();
186 break;
187
188 case WorkerThreadUnchangedBusyCount:
189 aWorkerPrivate->AssertIsOnWorkerThread();
190 break;
191
192 default:
193 MOZ_ASSUME_UNREACHABLE("Unknown behavior!");
194 }
195 #endif
196
197 if (mBehavior == WorkerThreadModifyBusyCount) {
198 if (!aWorkerPrivate->ModifyBusyCountFromWorker(aCx, false)) {
199 aRunResult = false;
200 }
201 }
202
203 if (!aRunResult) {
204 JS_ReportPendingException(aCx);
205 }
206 }
207
208 // static
209 WorkerRunnable*
210 WorkerRunnable::FromRunnable(nsIRunnable* aRunnable)
211 {
212 MOZ_ASSERT(aRunnable);
213
214 WorkerRunnable* runnable;
215 nsresult rv = aRunnable->QueryInterface(kWorkerRunnableIID,
216 reinterpret_cast<void**>(&runnable));
217 if (NS_FAILED(rv)) {
218 return nullptr;
219 }
220
221 MOZ_ASSERT(runnable);
222 return runnable;
223 }
224
225 NS_IMPL_ADDREF(WorkerRunnable)
226 NS_IMPL_RELEASE(WorkerRunnable)
227
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
239
240 NS_IMETHODIMP
241 WorkerRunnable::Run()
242 {
243 bool targetIsWorkerThread = mBehavior == WorkerThreadModifyBusyCount ||
244 mBehavior == WorkerThreadUnchangedBusyCount;
245
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
256
257 if (IsCanceled() && !mCallingCancelWithinRun) {
258 return NS_OK;
259 }
260
261 JSContext* cx;
262 nsRefPtr<WorkerPrivate> kungFuDeathGrip;
263 nsCxPusher pusher;
264
265 if (targetIsWorkerThread) {
266 if (mWorkerPrivate->AllPendingRunnablesShouldBeCanceled() &&
267 !IsCanceled() &&
268 !mCallingCancelWithinRun) {
269
270 // Prevent recursion.
271 mCallingCancelWithinRun = true;
272
273 Cancel();
274
275 MOZ_ASSERT(mCallingCancelWithinRun);
276 mCallingCancelWithinRun = false;
277
278 MOZ_ASSERT(IsCanceled(), "Subclass Cancel() didn't set IsCanceled()!");
279
280 return NS_OK;
281 }
282
283 cx = mWorkerPrivate->GetJSContext();
284 MOZ_ASSERT(cx);
285 }
286 else {
287 cx = mWorkerPrivate->ParentJSContext();
288 MOZ_ASSERT(cx);
289
290 kungFuDeathGrip = mWorkerPrivate;
291
292 if (!mWorkerPrivate->GetParent()) {
293 AssertIsOnMainThread();
294 pusher.Push(cx);
295 }
296 }
297
298 JSAutoRequest ar(cx);
299
300 JS::Rooted<JSObject*> targetCompartmentObject(cx);
301 if (targetIsWorkerThread) {
302 targetCompartmentObject = JS::CurrentGlobalOrNull(cx);
303 } else {
304 targetCompartmentObject = mWorkerPrivate->GetWrapper();
305 }
306
307 Maybe<JSAutoCompartment> ac;
308 if (targetCompartmentObject) {
309 ac.construct(cx, targetCompartmentObject);
310 }
311
312 bool result = WorkerRun(cx, mWorkerPrivate);
313
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 }
322
323 PostRun(cx, mWorkerPrivate, result);
324
325 return result ? NS_OK : NS_ERROR_FAILURE;
326 }
327
328 NS_IMETHODIMP
329 WorkerRunnable::Cancel()
330 {
331 uint32_t canceledCount = ++mCanceled;
332
333 MOZ_ASSERT(canceledCount, "Cancel() overflow!");
334
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 }
339
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 }
351
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 }
364
365 WorkerSyncRunnable::~WorkerSyncRunnable()
366 {
367 }
368
369 bool
370 WorkerSyncRunnable::DispatchInternal()
371 {
372 if (mSyncLoopTarget) {
373 return NS_SUCCEEDED(mSyncLoopTarget->Dispatch(this, NS_DISPATCH_NORMAL));
374 }
375
376 return WorkerRunnable::DispatchInternal();
377 }
378
379 void
380 MainThreadWorkerSyncRunnable::PostDispatch(JSContext* aCx,
381 WorkerPrivate* aWorkerPrivate,
382 bool aDispatchResult)
383 {
384 MaybeReportMainThreadException(aCx, aDispatchResult);
385 }
386
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 }
397
398 NS_IMETHODIMP
399 StopSyncLoopRunnable::Cancel()
400 {
401 nsresult rv = Run();
402 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Run() failed");
403
404 nsresult rv2 = WorkerSyncRunnable::Cancel();
405 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv2), "Cancel() failed");
406
407 return NS_FAILED(rv) ? rv : rv2;
408 }
409
410 bool
411 StopSyncLoopRunnable::WorkerRun(JSContext* aCx,
412 WorkerPrivate* aWorkerPrivate)
413 {
414 aWorkerPrivate->AssertIsOnWorkerThread();
415 MOZ_ASSERT(mSyncLoopTarget);
416
417 nsCOMPtr<nsIEventTarget> syncLoopTarget;
418 mSyncLoopTarget.swap(syncLoopTarget);
419
420 if (!mResult) {
421 MaybeSetException(aCx);
422 }
423
424 aWorkerPrivate->StopSyncLoop(syncLoopTarget, mResult);
425 return true;
426 }
427
428 bool
429 StopSyncLoopRunnable::DispatchInternal()
430 {
431 MOZ_ASSERT(mSyncLoopTarget);
432
433 return NS_SUCCEEDED(mSyncLoopTarget->Dispatch(this, NS_DISPATCH_NORMAL));
434 }
435
436 void
437 MainThreadStopSyncLoopRunnable::PostDispatch(JSContext* aCx,
438 WorkerPrivate* aWorkerPrivate,
439 bool aDispatchResult)
440 {
441 MaybeReportMainThreadException(aCx, aDispatchResult);
442 }
443
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
455
456 bool
457 WorkerControlRunnable::DispatchInternal()
458 {
459 if (mBehavior == WorkerThreadUnchangedBusyCount) {
460 return NS_SUCCEEDED(mWorkerPrivate->DispatchControlRunnable(this));
461 }
462
463 if (WorkerPrivate* parent = mWorkerPrivate->GetParent()) {
464 return NS_SUCCEEDED(parent->DispatchControlRunnable(this));
465 }
466
467 nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
468 MOZ_ASSERT(mainThread);
469
470 return NS_SUCCEEDED(mainThread->Dispatch(this, NS_DISPATCH_NORMAL));
471 }
472
473 void
474 MainThreadWorkerControlRunnable::PostDispatch(JSContext* aCx,
475 WorkerPrivate* aWorkerPrivate,
476 bool aDispatchResult)
477 {
478 AssertIsOnMainThread();
479
480 if (aCx && !aDispatchResult) {
481 JS_ReportPendingException(aCx);
482 }
483 }
484
485 NS_IMPL_ISUPPORTS_INHERITED0(WorkerControlRunnable, WorkerRunnable)
486
487 bool
488 WorkerSameThreadRunnable::PreDispatch(JSContext* aCx,
489 WorkerPrivate* aWorkerPrivate)
490 {
491 aWorkerPrivate->AssertIsOnWorkerThread();
492 return true;
493 }
494
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 }
508
509 void
510 WorkerSameThreadRunnable::PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
511 bool aRunResult)
512 {
513 MOZ_ASSERT(aCx);
514 MOZ_ASSERT(aWorkerPrivate);
515
516 aWorkerPrivate->AssertIsOnWorkerThread();
517
518 DebugOnly<bool> willDecrement = aWorkerPrivate->ModifyBusyCountFromWorker(aCx, false);
519 MOZ_ASSERT(willDecrement);
520
521 if (!aRunResult) {
522 JS_ReportPendingException(aCx);
523 }
524 }
525

mercurial