|
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 |