/src/mozilla-central/dom/workers/WorkerPrivate.cpp
Line | Count | Source (jump to first uncovered line) |
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 "WorkerPrivate.h" |
8 | | |
9 | | #include "js/CompilationAndEvaluation.h" |
10 | | #include "js/LocaleSensitive.h" |
11 | | #include "js/MemoryMetrics.h" |
12 | | #include "js/SourceBufferHolder.h" |
13 | | #include "MessageEventRunnable.h" |
14 | | #include "mozilla/ScopeExit.h" |
15 | | #include "mozilla/StaticPrefs.h" |
16 | | #include "mozilla/dom/BlobURLProtocolHandler.h" |
17 | | #include "mozilla/dom/ClientManager.h" |
18 | | #include "mozilla/dom/ClientSource.h" |
19 | | #include "mozilla/dom/ClientState.h" |
20 | | #include "mozilla/dom/Console.h" |
21 | | #include "mozilla/dom/DOMTypes.h" |
22 | | #include "mozilla/dom/ErrorEvent.h" |
23 | | #include "mozilla/dom/ErrorEventBinding.h" |
24 | | #include "mozilla/dom/Event.h" |
25 | | #include "mozilla/dom/FunctionBinding.h" |
26 | | #include "mozilla/dom/IndexedDatabaseManager.h" |
27 | | #include "mozilla/dom/MessageEvent.h" |
28 | | #include "mozilla/dom/MessageEventBinding.h" |
29 | | #include "mozilla/dom/MessagePort.h" |
30 | | #include "mozilla/dom/MessagePortBinding.h" |
31 | | #include "mozilla/dom/nsCSPUtils.h" |
32 | | #include "mozilla/dom/Performance.h" |
33 | | #include "mozilla/dom/PerformanceStorageWorker.h" |
34 | | #include "mozilla/dom/PromiseDebugging.h" |
35 | | #include "mozilla/dom/WorkerBinding.h" |
36 | | #include "mozilla/ThreadEventQueue.h" |
37 | | #include "mozilla/ThrottledEventQueue.h" |
38 | | #include "mozilla/TimelineConsumers.h" |
39 | | #include "mozilla/WorkerTimelineMarker.h" |
40 | | #include "nsCycleCollector.h" |
41 | | #include "nsGlobalWindowInner.h" |
42 | | #include "nsNetUtil.h" |
43 | | #include "nsIMemoryReporter.h" |
44 | | #include "nsIPermissionManager.h" |
45 | | #include "nsIRandomGenerator.h" |
46 | | #include "nsIScriptError.h" |
47 | | #include "nsIScriptTimeoutHandler.h" |
48 | | #include "nsIURI.h" |
49 | | #include "nsIURL.h" |
50 | | #include "nsPrintfCString.h" |
51 | | #include "nsQueryObject.h" |
52 | | #include "nsRFPService.h" |
53 | | #include "nsSandboxFlags.h" |
54 | | #include "nsUTF8Utils.h" |
55 | | |
56 | | #include "RuntimeService.h" |
57 | | #include "ScriptLoader.h" |
58 | | #include "mozilla/dom/ServiceWorkerEvents.h" |
59 | | #include "mozilla/dom/ServiceWorkerManager.h" |
60 | | #include "SharedWorker.h" |
61 | | #include "WorkerCSPEventListener.h" |
62 | | #include "WorkerDebugger.h" |
63 | | #include "WorkerDebuggerManager.h" |
64 | | #include "WorkerError.h" |
65 | | #include "WorkerEventTarget.h" |
66 | | #include "WorkerNavigator.h" |
67 | | #include "WorkerRef.h" |
68 | | #include "WorkerRunnable.h" |
69 | | #include "WorkerScope.h" |
70 | | #include "WorkerThread.h" |
71 | | |
72 | | #include "nsThreadManager.h" |
73 | | |
74 | | #ifdef XP_WIN |
75 | | #undef PostMessage |
76 | | #endif |
77 | | |
78 | | // JS_MaybeGC will run once every second during normal execution. |
79 | 0 | #define PERIODIC_GC_TIMER_DELAY_SEC 1 |
80 | | |
81 | | // A shrinking GC will run five seconds after the last event is processed. |
82 | 0 | #define IDLE_GC_TIMER_DELAY_SEC 5 |
83 | | |
84 | | static mozilla::LazyLogModule sWorkerPrivateLog("WorkerPrivate"); |
85 | | static mozilla::LazyLogModule sWorkerTimeoutsLog("WorkerTimeouts"); |
86 | | |
87 | | mozilla::LogModule* |
88 | | WorkerLog() |
89 | 0 | { |
90 | 0 | return sWorkerPrivateLog; |
91 | 0 | } |
92 | | |
93 | | mozilla::LogModule* |
94 | | TimeoutsLog() |
95 | 0 | { |
96 | 0 | return sWorkerTimeoutsLog; |
97 | 0 | } |
98 | | |
99 | | #ifdef LOG |
100 | | #undef LOG |
101 | | #endif |
102 | 0 | #define LOG(log, _args) MOZ_LOG(log, LogLevel::Debug, _args); |
103 | | |
104 | | namespace mozilla { |
105 | | |
106 | | using namespace ipc; |
107 | | |
108 | | namespace dom { |
109 | | |
110 | | using namespace workerinternals; |
111 | | |
112 | | MOZ_DEFINE_MALLOC_SIZE_OF(JsWorkerMallocSizeOf) |
113 | | |
114 | | namespace { |
115 | | |
116 | | #ifdef DEBUG |
117 | | |
118 | | const nsIID kDEBUGWorkerEventTargetIID = { |
119 | | 0xccaba3fa, 0x5be2, 0x4de2, { 0xba, 0x87, 0x3b, 0x3b, 0x5b, 0x1d, 0x5, 0xfb } |
120 | | }; |
121 | | |
122 | | #endif |
123 | | |
124 | | template <class T> |
125 | | class AutoPtrComparator |
126 | | { |
127 | | typedef nsAutoPtr<T> A; |
128 | | typedef T* B; |
129 | | |
130 | | public: |
131 | 0 | bool Equals(const A& a, const B& b) const { |
132 | 0 | return a && b ? *a == *b : !a && !b ? true : false; |
133 | 0 | } |
134 | 0 | bool LessThan(const A& a, const B& b) const { |
135 | 0 | return a && b ? *a < *b : b ? true : false; |
136 | 0 | } |
137 | | }; |
138 | | |
139 | | template <class T> |
140 | | inline AutoPtrComparator<T> |
141 | | GetAutoPtrComparator(const nsTArray<nsAutoPtr<T> >&) |
142 | 0 | { |
143 | 0 | return AutoPtrComparator<T>(); |
144 | 0 | } |
145 | | |
146 | | // This class is used to wrap any runnables that the worker receives via the |
147 | | // nsIEventTarget::Dispatch() method (either from NS_DispatchToCurrentThread or |
148 | | // from the worker's EventTarget). |
149 | | class ExternalRunnableWrapper final : public WorkerRunnable |
150 | | { |
151 | | nsCOMPtr<nsIRunnable> mWrappedRunnable; |
152 | | |
153 | | public: |
154 | | ExternalRunnableWrapper(WorkerPrivate* aWorkerPrivate, |
155 | | nsIRunnable* aWrappedRunnable) |
156 | | : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), |
157 | | mWrappedRunnable(aWrappedRunnable) |
158 | 0 | { |
159 | 0 | MOZ_ASSERT(aWorkerPrivate); |
160 | 0 | MOZ_ASSERT(aWrappedRunnable); |
161 | 0 | } |
162 | | |
163 | | NS_INLINE_DECL_REFCOUNTING_INHERITED(ExternalRunnableWrapper, WorkerRunnable) |
164 | | |
165 | | private: |
166 | | ~ExternalRunnableWrapper() |
167 | 0 | { } |
168 | | |
169 | | virtual bool |
170 | | PreDispatch(WorkerPrivate* aWorkerPrivate) override |
171 | 0 | { |
172 | 0 | // Silence bad assertions. |
173 | 0 | return true; |
174 | 0 | } |
175 | | |
176 | | virtual void |
177 | | PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override |
178 | 0 | { |
179 | 0 | // Silence bad assertions. |
180 | 0 | } |
181 | | |
182 | | virtual bool |
183 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
184 | 0 | { |
185 | 0 | nsresult rv = mWrappedRunnable->Run(); |
186 | 0 | if (NS_FAILED(rv)) { |
187 | 0 | if (!JS_IsExceptionPending(aCx)) { |
188 | 0 | Throw(aCx, rv); |
189 | 0 | } |
190 | 0 | return false; |
191 | 0 | } |
192 | 0 | return true; |
193 | 0 | } |
194 | | |
195 | | nsresult |
196 | | Cancel() override |
197 | 0 | { |
198 | 0 | nsresult rv; |
199 | 0 | nsCOMPtr<nsICancelableRunnable> cancelable = |
200 | 0 | do_QueryInterface(mWrappedRunnable); |
201 | 0 | MOZ_ASSERT(cancelable); // We checked this earlier! |
202 | 0 | rv = cancelable->Cancel(); |
203 | 0 | nsresult rv2 = WorkerRunnable::Cancel(); |
204 | 0 | return NS_FAILED(rv) ? rv : rv2; |
205 | 0 | } |
206 | | }; |
207 | | |
208 | | struct WindowAction |
209 | | { |
210 | | nsPIDOMWindowInner* mWindow; |
211 | | bool mDefaultAction; |
212 | | |
213 | | MOZ_IMPLICIT WindowAction(nsPIDOMWindowInner* aWindow) |
214 | | : mWindow(aWindow), mDefaultAction(true) |
215 | 0 | { } |
216 | | |
217 | | bool |
218 | | operator==(const WindowAction& aOther) const |
219 | 0 | { |
220 | 0 | return mWindow == aOther.mWindow; |
221 | 0 | } |
222 | | }; |
223 | | |
224 | | class WorkerFinishedRunnable final : public WorkerControlRunnable |
225 | | { |
226 | | WorkerPrivate* mFinishedWorker; |
227 | | |
228 | | public: |
229 | | WorkerFinishedRunnable(WorkerPrivate* aWorkerPrivate, |
230 | | WorkerPrivate* aFinishedWorker) |
231 | | : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), |
232 | | mFinishedWorker(aFinishedWorker) |
233 | 0 | { } |
234 | | |
235 | | private: |
236 | | virtual bool |
237 | | PreDispatch(WorkerPrivate* aWorkerPrivate) override |
238 | 0 | { |
239 | 0 | // Silence bad assertions. |
240 | 0 | return true; |
241 | 0 | } |
242 | | |
243 | | virtual void |
244 | | PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override |
245 | 0 | { |
246 | 0 | // Silence bad assertions. |
247 | 0 | } |
248 | | |
249 | | virtual bool |
250 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
251 | 0 | { |
252 | 0 | if (!mFinishedWorker->ProxyReleaseMainThreadObjects()) { |
253 | 0 | NS_WARNING("Failed to dispatch, going to leak!"); |
254 | 0 | } |
255 | 0 |
|
256 | 0 | RuntimeService* runtime = RuntimeService::GetService(); |
257 | 0 | NS_ASSERTION(runtime, "This should never be null!"); |
258 | 0 |
|
259 | 0 | mFinishedWorker->DisableDebugger(); |
260 | 0 |
|
261 | 0 | runtime->UnregisterWorker(mFinishedWorker); |
262 | 0 |
|
263 | 0 | mFinishedWorker->ClearSelfAndParentEventTargetRef(); |
264 | 0 | return true; |
265 | 0 | } |
266 | | }; |
267 | | |
268 | | class TopLevelWorkerFinishedRunnable final : public Runnable |
269 | | { |
270 | | WorkerPrivate* mFinishedWorker; |
271 | | |
272 | | public: |
273 | | explicit TopLevelWorkerFinishedRunnable(WorkerPrivate* aFinishedWorker) |
274 | | : mozilla::Runnable("TopLevelWorkerFinishedRunnable") |
275 | | , mFinishedWorker(aFinishedWorker) |
276 | 0 | { |
277 | 0 | aFinishedWorker->AssertIsOnWorkerThread(); |
278 | 0 | } |
279 | | |
280 | | NS_INLINE_DECL_REFCOUNTING_INHERITED(TopLevelWorkerFinishedRunnable, Runnable) |
281 | | |
282 | | private: |
283 | 0 | ~TopLevelWorkerFinishedRunnable() {} |
284 | | |
285 | | NS_IMETHOD |
286 | | Run() override |
287 | 0 | { |
288 | 0 | AssertIsOnMainThread(); |
289 | 0 |
|
290 | 0 | RuntimeService* runtime = RuntimeService::GetService(); |
291 | 0 | MOZ_ASSERT(runtime); |
292 | 0 |
|
293 | 0 | mFinishedWorker->DisableDebugger(); |
294 | 0 |
|
295 | 0 | runtime->UnregisterWorker(mFinishedWorker); |
296 | 0 |
|
297 | 0 | if (!mFinishedWorker->ProxyReleaseMainThreadObjects()) { |
298 | 0 | NS_WARNING("Failed to dispatch, going to leak!"); |
299 | 0 | } |
300 | 0 |
|
301 | 0 | mFinishedWorker->ClearSelfAndParentEventTargetRef(); |
302 | 0 | return NS_OK; |
303 | 0 | } |
304 | | }; |
305 | | |
306 | | class ModifyBusyCountRunnable final : public WorkerControlRunnable |
307 | | { |
308 | | bool mIncrease; |
309 | | |
310 | | public: |
311 | | ModifyBusyCountRunnable(WorkerPrivate* aWorkerPrivate, bool aIncrease) |
312 | | : WorkerControlRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount), |
313 | | mIncrease(aIncrease) |
314 | 0 | { } |
315 | | |
316 | | private: |
317 | | virtual bool |
318 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
319 | 0 | { |
320 | 0 | return aWorkerPrivate->ModifyBusyCount(mIncrease); |
321 | 0 | } |
322 | | |
323 | | virtual void |
324 | | PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult) |
325 | | override |
326 | 0 | { |
327 | 0 | if (mIncrease) { |
328 | 0 | WorkerControlRunnable::PostRun(aCx, aWorkerPrivate, aRunResult); |
329 | 0 | return; |
330 | 0 | } |
331 | 0 | // Don't do anything here as it's possible that aWorkerPrivate has been |
332 | 0 | // deleted. |
333 | 0 | } |
334 | | }; |
335 | | |
336 | | class CompileScriptRunnable final : public WorkerRunnable |
337 | | { |
338 | | nsString mScriptURL; |
339 | | |
340 | | public: |
341 | | explicit CompileScriptRunnable(WorkerPrivate* aWorkerPrivate, |
342 | | const nsAString& aScriptURL) |
343 | | : WorkerRunnable(aWorkerPrivate), |
344 | | mScriptURL(aScriptURL) |
345 | 0 | { } |
346 | | |
347 | | private: |
348 | | // We can't implement PreRun effectively, because at the point when that would |
349 | | // run we have not yet done our load so don't know things like our final |
350 | | // principal and whatnot. |
351 | | |
352 | | virtual bool |
353 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
354 | 0 | { |
355 | 0 | aWorkerPrivate->AssertIsOnWorkerThread(); |
356 | 0 |
|
357 | 0 | if (NS_WARN_IF(!aWorkerPrivate->EnsureClientSource())) { |
358 | 0 | return false; |
359 | 0 | } |
360 | 0 | |
361 | 0 | if (NS_WARN_IF(!aWorkerPrivate->EnsureCSPEventListener())) { |
362 | 0 | return false; |
363 | 0 | } |
364 | 0 | |
365 | 0 | // PerformanceStorage & PerformanceCounter both need to be initialized |
366 | 0 | // on the worker thread before being used on main-thread. |
367 | 0 | // Let's be sure that it is created before any |
368 | 0 | // content loading. |
369 | 0 | aWorkerPrivate->EnsurePerformanceStorage(); |
370 | 0 |
|
371 | 0 | if (mozilla::StaticPrefs::dom_performance_enable_scheduler_timing()) { |
372 | 0 | aWorkerPrivate->EnsurePerformanceCounter(); |
373 | 0 | } |
374 | 0 |
|
375 | 0 | ErrorResult rv; |
376 | 0 | workerinternals::LoadMainScript(aWorkerPrivate, mScriptURL, WorkerScript, rv); |
377 | 0 | rv.WouldReportJSException(); |
378 | 0 | // Explicitly ignore NS_BINDING_ABORTED on rv. Or more precisely, still |
379 | 0 | // return false and don't SetWorkerScriptExecutedSuccessfully() in that |
380 | 0 | // case, but don't throw anything on aCx. The idea is to not dispatch error |
381 | 0 | // events if our load is canceled with that error code. |
382 | 0 | if (rv.ErrorCodeIs(NS_BINDING_ABORTED)) { |
383 | 0 | rv.SuppressException(); |
384 | 0 | return false; |
385 | 0 | } |
386 | 0 | |
387 | 0 | WorkerGlobalScope* globalScope = aWorkerPrivate->GlobalScope(); |
388 | 0 | if (NS_WARN_IF(!globalScope)) { |
389 | 0 | // We never got as far as calling GetOrCreateGlobalScope, or it failed. |
390 | 0 | // We have no way to enter a compartment, hence no sane way to report this |
391 | 0 | // error. :( |
392 | 0 | rv.SuppressException(); |
393 | 0 | return false; |
394 | 0 | } |
395 | 0 | |
396 | 0 | // Make sure to propagate exceptions from rv onto aCx, so that they will get |
397 | 0 | // reported after we return. We want to propagate just JS exceptions, |
398 | 0 | // because all the other errors are handled when the script is loaded. |
399 | 0 | // See: https://dom.spec.whatwg.org/#concept-event-fire |
400 | 0 | if (rv.Failed() && !rv.IsJSException()) { |
401 | 0 | WorkerErrorReport::CreateAndDispatchGenericErrorRunnableToParent(aWorkerPrivate); |
402 | 0 | rv.SuppressException(); |
403 | 0 | return false; |
404 | 0 | } |
405 | 0 | |
406 | 0 | // This is a little dumb, but aCx is in the null realm here because we |
407 | 0 | // set it up that way in our Run(), since we had not created the global at |
408 | 0 | // that point yet. So we need to enter the realm of our global, |
409 | 0 | // because setting a pending exception on aCx involves wrapping into its |
410 | 0 | // current compartment. Luckily we have a global now. |
411 | 0 | JSAutoRealm ar(aCx, globalScope->GetGlobalJSObject()); |
412 | 0 | if (rv.MaybeSetPendingException(aCx)) { |
413 | 0 | return false; |
414 | 0 | } |
415 | 0 | |
416 | 0 | aWorkerPrivate->SetWorkerScriptExecutedSuccessfully(); |
417 | 0 | return true; |
418 | 0 | } |
419 | | |
420 | | void |
421 | | PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult) override |
422 | 0 | { |
423 | 0 | if (!aRunResult) { |
424 | 0 | aWorkerPrivate->CloseInternal(); |
425 | 0 | } |
426 | 0 | WorkerRunnable::PostRun(aCx, aWorkerPrivate, aRunResult); |
427 | 0 | } |
428 | | }; |
429 | | |
430 | | class NotifyRunnable final : public WorkerControlRunnable |
431 | | { |
432 | | WorkerStatus mStatus; |
433 | | |
434 | | public: |
435 | | NotifyRunnable(WorkerPrivate* aWorkerPrivate, WorkerStatus aStatus) |
436 | | : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), |
437 | | mStatus(aStatus) |
438 | 0 | { |
439 | 0 | MOZ_ASSERT(aStatus == Closing || aStatus == Canceling || |
440 | 0 | aStatus == Killing); |
441 | 0 | } |
442 | | |
443 | | private: |
444 | | virtual bool |
445 | | PreDispatch(WorkerPrivate* aWorkerPrivate) override |
446 | 0 | { |
447 | 0 | aWorkerPrivate->AssertIsOnParentThread(); |
448 | 0 | return aWorkerPrivate->ModifyBusyCount(true); |
449 | 0 | } |
450 | | |
451 | | virtual void |
452 | | PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override |
453 | 0 | { |
454 | 0 | aWorkerPrivate->AssertIsOnParentThread(); |
455 | 0 | if (!aDispatchResult) { |
456 | 0 | // We couldn't dispatch to the worker, which means it's already dead. |
457 | 0 | // Undo the busy count modification. |
458 | 0 | aWorkerPrivate->ModifyBusyCount(false); |
459 | 0 | } |
460 | 0 | } |
461 | | |
462 | | virtual void |
463 | | PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult) |
464 | | override |
465 | 0 | { |
466 | 0 | aWorkerPrivate->ModifyBusyCountFromWorker(false); |
467 | 0 | } |
468 | | |
469 | | virtual bool |
470 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
471 | 0 | { |
472 | 0 | return aWorkerPrivate->NotifyInternal(mStatus); |
473 | 0 | } |
474 | | }; |
475 | | |
476 | | class FreezeRunnable final : public WorkerControlRunnable |
477 | | { |
478 | | public: |
479 | | explicit FreezeRunnable(WorkerPrivate* aWorkerPrivate) |
480 | | : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) |
481 | 0 | { } |
482 | | |
483 | | private: |
484 | | virtual bool |
485 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
486 | 0 | { |
487 | 0 | return aWorkerPrivate->FreezeInternal(); |
488 | 0 | } |
489 | | }; |
490 | | |
491 | | class ThawRunnable final : public WorkerControlRunnable |
492 | | { |
493 | | public: |
494 | | explicit ThawRunnable(WorkerPrivate* aWorkerPrivate) |
495 | | : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) |
496 | 0 | { } |
497 | | |
498 | | private: |
499 | | virtual bool |
500 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
501 | 0 | { |
502 | 0 | return aWorkerPrivate->ThawInternal(); |
503 | 0 | } |
504 | | }; |
505 | | |
506 | | class PropagateFirstPartyStorageAccessGrantedRunnable final : public WorkerControlRunnable |
507 | | { |
508 | | public: |
509 | | explicit PropagateFirstPartyStorageAccessGrantedRunnable(WorkerPrivate* aWorkerPrivate) |
510 | | : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) |
511 | 0 | {} |
512 | | |
513 | | private: |
514 | | bool |
515 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
516 | 0 | { |
517 | 0 | aWorkerPrivate->PropagateFirstPartyStorageAccessGrantedInternal(); |
518 | 0 | return true; |
519 | 0 | } |
520 | | }; |
521 | | |
522 | | class ReportErrorToConsoleRunnable final : public WorkerRunnable |
523 | | { |
524 | | const char* mMessage; |
525 | | const nsTArray<nsString> mParams; |
526 | | |
527 | | public: |
528 | | // aWorkerPrivate is the worker thread we're on (or the main thread, if null) |
529 | | static void |
530 | | Report(WorkerPrivate* aWorkerPrivate, const char* aMessage, |
531 | | const nsTArray<nsString>& aParams) |
532 | 0 | { |
533 | 0 | if (aWorkerPrivate) { |
534 | 0 | aWorkerPrivate->AssertIsOnWorkerThread(); |
535 | 0 | } else { |
536 | 0 | AssertIsOnMainThread(); |
537 | 0 | } |
538 | 0 |
|
539 | 0 | // Now fire a runnable to do the same on the parent's thread if we can. |
540 | 0 | if (aWorkerPrivate) { |
541 | 0 | RefPtr<ReportErrorToConsoleRunnable> runnable = |
542 | 0 | new ReportErrorToConsoleRunnable(aWorkerPrivate, aMessage, aParams); |
543 | 0 | runnable->Dispatch(); |
544 | 0 | return; |
545 | 0 | } |
546 | 0 | |
547 | 0 | uint16_t paramCount = aParams.Length(); |
548 | 0 | const char16_t** params = new const char16_t*[paramCount]; |
549 | 0 | for (uint16_t i=0; i<paramCount; ++i) { |
550 | 0 | params[i] = aParams[i].get(); |
551 | 0 | } |
552 | 0 |
|
553 | 0 | // Log a warning to the console. |
554 | 0 | nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, |
555 | 0 | NS_LITERAL_CSTRING("DOM"), nullptr, |
556 | 0 | nsContentUtils::eDOM_PROPERTIES, aMessage, |
557 | 0 | paramCount ? params : nullptr, paramCount); |
558 | 0 | delete[] params; |
559 | 0 | } |
560 | | |
561 | | private: |
562 | | ReportErrorToConsoleRunnable(WorkerPrivate* aWorkerPrivate, const char* aMessage, |
563 | | const nsTArray<nsString>& aParams) |
564 | | : WorkerRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount), |
565 | | mMessage(aMessage), mParams(aParams) |
566 | 0 | { } |
567 | | |
568 | | virtual void |
569 | | PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override |
570 | 0 | { |
571 | 0 | aWorkerPrivate->AssertIsOnWorkerThread(); |
572 | 0 |
|
573 | 0 | // Dispatch may fail if the worker was canceled, no need to report that as |
574 | 0 | // an error, so don't call base class PostDispatch. |
575 | 0 | } |
576 | | |
577 | | virtual bool |
578 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
579 | 0 | { |
580 | 0 | WorkerPrivate* parent = aWorkerPrivate->GetParent(); |
581 | 0 | MOZ_ASSERT_IF(!parent, NS_IsMainThread()); |
582 | 0 | Report(parent, mMessage, mParams); |
583 | 0 | return true; |
584 | 0 | } |
585 | | }; |
586 | | |
587 | | class TimerRunnable final : public WorkerRunnable, |
588 | | public nsITimerCallback, |
589 | | public nsINamed |
590 | | { |
591 | | public: |
592 | | NS_DECL_ISUPPORTS_INHERITED |
593 | | |
594 | | explicit TimerRunnable(WorkerPrivate* aWorkerPrivate) |
595 | | : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) |
596 | 0 | { } |
597 | | |
598 | | private: |
599 | 0 | ~TimerRunnable() {} |
600 | | |
601 | | virtual bool |
602 | | PreDispatch(WorkerPrivate* aWorkerPrivate) override |
603 | 0 | { |
604 | 0 | // Silence bad assertions. |
605 | 0 | return true; |
606 | 0 | } |
607 | | |
608 | | virtual void |
609 | | PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override |
610 | 0 | { |
611 | 0 | // Silence bad assertions. |
612 | 0 | } |
613 | | |
614 | | virtual bool |
615 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
616 | 0 | { |
617 | 0 | return aWorkerPrivate->RunExpiredTimeouts(aCx); |
618 | 0 | } |
619 | | |
620 | | NS_IMETHOD |
621 | | Notify(nsITimer* aTimer) override |
622 | 0 | { |
623 | 0 | return Run(); |
624 | 0 | } |
625 | | |
626 | | NS_IMETHOD |
627 | | GetName(nsACString& aName) override |
628 | 0 | { |
629 | 0 | aName.AssignLiteral("TimerRunnable"); |
630 | 0 | return NS_OK; |
631 | 0 | } |
632 | | }; |
633 | | |
634 | | NS_IMPL_ISUPPORTS_INHERITED(TimerRunnable, WorkerRunnable, nsITimerCallback, |
635 | | nsINamed) |
636 | | |
637 | | class DebuggerImmediateRunnable : public WorkerRunnable |
638 | | { |
639 | | RefPtr<dom::Function> mHandler; |
640 | | |
641 | | public: |
642 | | explicit DebuggerImmediateRunnable(WorkerPrivate* aWorkerPrivate, |
643 | | dom::Function& aHandler) |
644 | | : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), |
645 | | mHandler(&aHandler) |
646 | 0 | { } |
647 | | |
648 | | private: |
649 | | virtual bool |
650 | | IsDebuggerRunnable() const override |
651 | 0 | { |
652 | 0 | return true; |
653 | 0 | } |
654 | | |
655 | | virtual bool |
656 | | PreDispatch(WorkerPrivate* aWorkerPrivate) override |
657 | 0 | { |
658 | 0 | // Silence bad assertions. |
659 | 0 | return true; |
660 | 0 | } |
661 | | |
662 | | virtual void |
663 | | PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override |
664 | 0 | { |
665 | 0 | // Silence bad assertions. |
666 | 0 | } |
667 | | |
668 | | virtual bool |
669 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
670 | 0 | { |
671 | 0 | JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx)); |
672 | 0 | JS::Rooted<JS::Value> callable(aCx, JS::ObjectOrNullValue(mHandler->CallableOrNull())); |
673 | 0 | JS::HandleValueArray args = JS::HandleValueArray::empty(); |
674 | 0 | JS::Rooted<JS::Value> rval(aCx); |
675 | 0 | if (!JS_CallFunctionValue(aCx, global, callable, args, &rval)) { |
676 | 0 | // Just return false; WorkerRunnable::Run will report the exception. |
677 | 0 | return false; |
678 | 0 | } |
679 | 0 | |
680 | 0 | return true; |
681 | 0 | } |
682 | | }; |
683 | | |
684 | | void |
685 | | PeriodicGCTimerCallback(nsITimer* aTimer, void* aClosure) |
686 | 0 | { |
687 | 0 | auto workerPrivate = static_cast<WorkerPrivate*>(aClosure); |
688 | 0 | MOZ_DIAGNOSTIC_ASSERT(workerPrivate); |
689 | 0 | workerPrivate->AssertIsOnWorkerThread(); |
690 | 0 | workerPrivate->GarbageCollectInternal(workerPrivate->GetJSContext(), |
691 | 0 | false /* shrinking */, |
692 | 0 | false /* collect children */); |
693 | 0 | } |
694 | | |
695 | | void |
696 | | IdleGCTimerCallback(nsITimer* aTimer, void* aClosure) |
697 | 0 | { |
698 | 0 | auto workerPrivate = static_cast<WorkerPrivate*>(aClosure); |
699 | 0 | MOZ_DIAGNOSTIC_ASSERT(workerPrivate); |
700 | 0 | workerPrivate->AssertIsOnWorkerThread(); |
701 | 0 | workerPrivate->GarbageCollectInternal(workerPrivate->GetJSContext(), |
702 | 0 | true /* shrinking */, |
703 | 0 | false /* collect children */); |
704 | 0 | } |
705 | | |
706 | | class UpdateContextOptionsRunnable final : public WorkerControlRunnable |
707 | | { |
708 | | JS::ContextOptions mContextOptions; |
709 | | |
710 | | public: |
711 | | UpdateContextOptionsRunnable(WorkerPrivate* aWorkerPrivate, |
712 | | const JS::ContextOptions& aContextOptions) |
713 | | : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), |
714 | | mContextOptions(aContextOptions) |
715 | 0 | { } |
716 | | |
717 | | private: |
718 | | virtual bool |
719 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
720 | 0 | { |
721 | 0 | aWorkerPrivate->UpdateContextOptionsInternal(aCx, mContextOptions); |
722 | 0 | return true; |
723 | 0 | } |
724 | | }; |
725 | | |
726 | | class UpdateLanguagesRunnable final : public WorkerRunnable |
727 | | { |
728 | | nsTArray<nsString> mLanguages; |
729 | | |
730 | | public: |
731 | | UpdateLanguagesRunnable(WorkerPrivate* aWorkerPrivate, |
732 | | const nsTArray<nsString>& aLanguages) |
733 | | : WorkerRunnable(aWorkerPrivate), |
734 | | mLanguages(aLanguages) |
735 | 0 | { } |
736 | | |
737 | | virtual bool |
738 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
739 | 0 | { |
740 | 0 | aWorkerPrivate->UpdateLanguagesInternal(mLanguages); |
741 | 0 | return true; |
742 | 0 | } |
743 | | }; |
744 | | |
745 | | class UpdateJSWorkerMemoryParameterRunnable final : |
746 | | public WorkerControlRunnable |
747 | | { |
748 | | uint32_t mValue; |
749 | | JSGCParamKey mKey; |
750 | | |
751 | | public: |
752 | | UpdateJSWorkerMemoryParameterRunnable(WorkerPrivate* aWorkerPrivate, |
753 | | JSGCParamKey aKey, |
754 | | uint32_t aValue) |
755 | | : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), |
756 | | mValue(aValue), mKey(aKey) |
757 | 0 | { } |
758 | | |
759 | | private: |
760 | | virtual bool |
761 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
762 | 0 | { |
763 | 0 | aWorkerPrivate->UpdateJSWorkerMemoryParameterInternal(aCx, mKey, mValue); |
764 | 0 | return true; |
765 | 0 | } |
766 | | }; |
767 | | |
768 | | #ifdef JS_GC_ZEAL |
769 | | class UpdateGCZealRunnable final : public WorkerControlRunnable |
770 | | { |
771 | | uint8_t mGCZeal; |
772 | | uint32_t mFrequency; |
773 | | |
774 | | public: |
775 | | UpdateGCZealRunnable(WorkerPrivate* aWorkerPrivate, |
776 | | uint8_t aGCZeal, |
777 | | uint32_t aFrequency) |
778 | | : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), |
779 | | mGCZeal(aGCZeal), mFrequency(aFrequency) |
780 | | { } |
781 | | |
782 | | private: |
783 | | virtual bool |
784 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
785 | | { |
786 | | aWorkerPrivate->UpdateGCZealInternal(aCx, mGCZeal, mFrequency); |
787 | | return true; |
788 | | } |
789 | | }; |
790 | | #endif |
791 | | |
792 | | class GarbageCollectRunnable final : public WorkerControlRunnable |
793 | | { |
794 | | bool mShrinking; |
795 | | bool mCollectChildren; |
796 | | |
797 | | public: |
798 | | GarbageCollectRunnable(WorkerPrivate* aWorkerPrivate, bool aShrinking, |
799 | | bool aCollectChildren) |
800 | | : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), |
801 | | mShrinking(aShrinking), mCollectChildren(aCollectChildren) |
802 | 0 | { } |
803 | | |
804 | | private: |
805 | | virtual bool |
806 | | PreDispatch(WorkerPrivate* aWorkerPrivate) override |
807 | 0 | { |
808 | 0 | // Silence bad assertions, this can be dispatched from either the main |
809 | 0 | // thread or the timer thread.. |
810 | 0 | return true; |
811 | 0 | } |
812 | | |
813 | | virtual void |
814 | | PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override |
815 | 0 | { |
816 | 0 | // Silence bad assertions, this can be dispatched from either the main |
817 | 0 | // thread or the timer thread.. |
818 | 0 | } |
819 | | |
820 | | virtual bool |
821 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
822 | 0 | { |
823 | 0 | aWorkerPrivate->GarbageCollectInternal(aCx, mShrinking, mCollectChildren); |
824 | 0 | return true; |
825 | 0 | } |
826 | | }; |
827 | | |
828 | | class CycleCollectRunnable : public WorkerControlRunnable |
829 | | { |
830 | | bool mCollectChildren; |
831 | | |
832 | | public: |
833 | | CycleCollectRunnable(WorkerPrivate* aWorkerPrivate, bool aCollectChildren) |
834 | | : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), |
835 | | mCollectChildren(aCollectChildren) |
836 | 0 | { } |
837 | | |
838 | | bool |
839 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
840 | 0 | { |
841 | 0 | aWorkerPrivate->CycleCollectInternal(mCollectChildren); |
842 | 0 | return true; |
843 | 0 | } |
844 | | }; |
845 | | |
846 | | class OfflineStatusChangeRunnable : public WorkerRunnable |
847 | | { |
848 | | public: |
849 | | OfflineStatusChangeRunnable(WorkerPrivate* aWorkerPrivate, bool aIsOffline) |
850 | | : WorkerRunnable(aWorkerPrivate), |
851 | | mIsOffline(aIsOffline) |
852 | 0 | { |
853 | 0 | } |
854 | | |
855 | | bool |
856 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
857 | 0 | { |
858 | 0 | aWorkerPrivate->OfflineStatusChangeEventInternal(mIsOffline); |
859 | 0 | return true; |
860 | 0 | } |
861 | | |
862 | | private: |
863 | | bool mIsOffline; |
864 | | }; |
865 | | |
866 | | class MemoryPressureRunnable : public WorkerControlRunnable |
867 | | { |
868 | | public: |
869 | | explicit MemoryPressureRunnable(WorkerPrivate* aWorkerPrivate) |
870 | | : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) |
871 | 0 | {} |
872 | | |
873 | | bool |
874 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
875 | 0 | { |
876 | 0 | aWorkerPrivate->MemoryPressureInternal(); |
877 | 0 | return true; |
878 | 0 | } |
879 | | }; |
880 | | |
881 | | #ifdef DEBUG |
882 | | static bool |
883 | | StartsWithExplicit(nsACString& s) |
884 | | { |
885 | | return StringBeginsWith(s, NS_LITERAL_CSTRING("explicit/")); |
886 | | } |
887 | | #endif |
888 | | |
889 | | class MessagePortRunnable final : public WorkerRunnable |
890 | | { |
891 | | MessagePortIdentifier mPortIdentifier; |
892 | | |
893 | | public: |
894 | | MessagePortRunnable(WorkerPrivate* aWorkerPrivate, MessagePort* aPort) |
895 | | : WorkerRunnable(aWorkerPrivate) |
896 | 0 | { |
897 | 0 | MOZ_ASSERT(aPort); |
898 | 0 | // In order to move the port from one thread to another one, we have to |
899 | 0 | // close and disentangle it. The output will be a MessagePortIdentifier that |
900 | 0 | // will be used to recreate a new MessagePort on the other thread. |
901 | 0 | aPort->CloneAndDisentangle(mPortIdentifier); |
902 | 0 | } |
903 | | |
904 | | private: |
905 | | ~MessagePortRunnable() |
906 | 0 | { } |
907 | | |
908 | | virtual bool |
909 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
910 | 0 | { |
911 | 0 | return aWorkerPrivate->ConnectMessagePort(aCx, mPortIdentifier); |
912 | 0 | } |
913 | | |
914 | | nsresult |
915 | | Cancel() override |
916 | 0 | { |
917 | 0 | MessagePort::ForceClose(mPortIdentifier); |
918 | 0 | return WorkerRunnable::Cancel(); |
919 | 0 | } |
920 | | }; |
921 | | |
922 | | PRThread* |
923 | | PRThreadFromThread(nsIThread* aThread) |
924 | 0 | { |
925 | 0 | MOZ_ASSERT(aThread); |
926 | 0 |
|
927 | 0 | PRThread* result; |
928 | 0 | MOZ_ALWAYS_SUCCEEDS(aThread->GetPRThread(&result)); |
929 | 0 | MOZ_ASSERT(result); |
930 | 0 |
|
931 | 0 | return result; |
932 | 0 | } |
933 | | |
934 | | // A runnable to cancel the worker from the parent thread when self.close() is |
935 | | // called. This runnable is executed on the parent process in order to cancel |
936 | | // the current runnable. It uses a normal WorkerRunnable in order to be sure |
937 | | // that all the pending WorkerRunnables are executed before this. |
938 | | class CancelingOnParentRunnable final : public WorkerRunnable |
939 | | { |
940 | | public: |
941 | | explicit CancelingOnParentRunnable(WorkerPrivate* aWorkerPrivate) |
942 | | : WorkerRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount) |
943 | 0 | {} |
944 | | |
945 | | bool |
946 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
947 | 0 | { |
948 | 0 | aWorkerPrivate->Cancel(); |
949 | 0 | return true; |
950 | 0 | } |
951 | | }; |
952 | | |
953 | | // A runnable to cancel the worker from the parent process. |
954 | | class CancelingWithTimeoutOnParentRunnable final : public WorkerControlRunnable |
955 | | { |
956 | | public: |
957 | | explicit CancelingWithTimeoutOnParentRunnable(WorkerPrivate* aWorkerPrivate) |
958 | | : WorkerControlRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount) |
959 | 0 | {} |
960 | | |
961 | | bool |
962 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
963 | 0 | { |
964 | 0 | aWorkerPrivate->AssertIsOnParentThread(); |
965 | 0 | aWorkerPrivate->StartCancelingTimer(); |
966 | 0 | return true; |
967 | 0 | } |
968 | | }; |
969 | | |
970 | | class CancelingTimerCallback final : public nsITimerCallback |
971 | | { |
972 | | public: |
973 | | NS_DECL_ISUPPORTS |
974 | | |
975 | | explicit CancelingTimerCallback(WorkerPrivate* aWorkerPrivate) |
976 | | : mWorkerPrivate(aWorkerPrivate) |
977 | 0 | {} |
978 | | |
979 | | NS_IMETHOD |
980 | | Notify(nsITimer* aTimer) override |
981 | 0 | { |
982 | 0 | mWorkerPrivate->AssertIsOnParentThread(); |
983 | 0 | mWorkerPrivate->Cancel(); |
984 | 0 | return NS_OK; |
985 | 0 | } |
986 | | |
987 | | private: |
988 | | ~CancelingTimerCallback() = default; |
989 | | |
990 | | // Raw pointer here is OK because the timer is canceled during the shutdown |
991 | | // steps. |
992 | | WorkerPrivate* mWorkerPrivate; |
993 | | }; |
994 | | |
995 | | NS_IMPL_ISUPPORTS(CancelingTimerCallback, nsITimerCallback) |
996 | | |
997 | | // This runnable starts the canceling of a worker after a self.close(). |
998 | | class CancelingRunnable final : public Runnable |
999 | | { |
1000 | | public: |
1001 | | CancelingRunnable() |
1002 | | : Runnable("CancelingRunnable") |
1003 | 0 | {} |
1004 | | |
1005 | | NS_IMETHOD |
1006 | | Run() override |
1007 | 0 | { |
1008 | 0 | WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); |
1009 | 0 | MOZ_ASSERT(workerPrivate); |
1010 | 0 | workerPrivate->AssertIsOnWorkerThread(); |
1011 | 0 |
|
1012 | 0 | // Now we can cancel the this worker from the parent process. |
1013 | 0 | RefPtr<CancelingOnParentRunnable> r = |
1014 | 0 | new CancelingOnParentRunnable(workerPrivate); |
1015 | 0 | r->Dispatch(); |
1016 | 0 |
|
1017 | 0 | return NS_OK; |
1018 | 0 | } |
1019 | | }; |
1020 | | |
1021 | | } /* anonymous namespace */ |
1022 | | |
1023 | | class WorkerPrivate::EventTarget final : public nsISerialEventTarget |
1024 | | { |
1025 | | // This mutex protects mWorkerPrivate and must be acquired *before* the |
1026 | | // WorkerPrivate's mutex whenever they must both be held. |
1027 | | mozilla::Mutex mMutex; |
1028 | | WorkerPrivate* mWorkerPrivate; |
1029 | | nsIEventTarget* mWeakNestedEventTarget; |
1030 | | nsCOMPtr<nsIEventTarget> mNestedEventTarget; |
1031 | | |
1032 | | public: |
1033 | | explicit EventTarget(WorkerPrivate* aWorkerPrivate) |
1034 | | : mMutex("WorkerPrivate::EventTarget::mMutex"), |
1035 | | mWorkerPrivate(aWorkerPrivate), mWeakNestedEventTarget(nullptr) |
1036 | 0 | { |
1037 | 0 | MOZ_ASSERT(aWorkerPrivate); |
1038 | 0 | } |
1039 | | |
1040 | | EventTarget(WorkerPrivate* aWorkerPrivate, nsIEventTarget* aNestedEventTarget) |
1041 | | : mMutex("WorkerPrivate::EventTarget::mMutex"), |
1042 | | mWorkerPrivate(aWorkerPrivate), mWeakNestedEventTarget(aNestedEventTarget), |
1043 | | mNestedEventTarget(aNestedEventTarget) |
1044 | 0 | { |
1045 | 0 | MOZ_ASSERT(aWorkerPrivate); |
1046 | 0 | MOZ_ASSERT(aNestedEventTarget); |
1047 | 0 | } |
1048 | | |
1049 | | void |
1050 | | Disable() |
1051 | 0 | { |
1052 | 0 | nsCOMPtr<nsIEventTarget> nestedEventTarget; |
1053 | 0 | { |
1054 | 0 | MutexAutoLock lock(mMutex); |
1055 | 0 |
|
1056 | 0 | // Note, Disable() can be called more than once safely. |
1057 | 0 | mWorkerPrivate = nullptr; |
1058 | 0 | mNestedEventTarget.swap(nestedEventTarget); |
1059 | 0 | } |
1060 | 0 | } |
1061 | | |
1062 | | nsIEventTarget* |
1063 | | GetWeakNestedEventTarget() const |
1064 | 0 | { |
1065 | 0 | MOZ_ASSERT(mWeakNestedEventTarget); |
1066 | 0 | return mWeakNestedEventTarget; |
1067 | 0 | } |
1068 | | |
1069 | | NS_DECL_THREADSAFE_ISUPPORTS |
1070 | | NS_DECL_NSIEVENTTARGET_FULL |
1071 | | |
1072 | | private: |
1073 | | ~EventTarget() |
1074 | 0 | { } |
1075 | | }; |
1076 | | |
1077 | | struct WorkerPrivate::TimeoutInfo |
1078 | | { |
1079 | | TimeoutInfo() |
1080 | | : mId(0), mIsInterval(false), mCanceled(false) |
1081 | 0 | { |
1082 | 0 | MOZ_COUNT_CTOR(mozilla::dom::WorkerPrivate::TimeoutInfo); |
1083 | 0 | } |
1084 | | |
1085 | | ~TimeoutInfo() |
1086 | 0 | { |
1087 | 0 | MOZ_COUNT_DTOR(mozilla::dom::WorkerPrivate::TimeoutInfo); |
1088 | 0 | } |
1089 | | |
1090 | | bool operator==(const TimeoutInfo& aOther) |
1091 | 0 | { |
1092 | 0 | return mTargetTime == aOther.mTargetTime; |
1093 | 0 | } |
1094 | | |
1095 | | bool operator<(const TimeoutInfo& aOther) |
1096 | 0 | { |
1097 | 0 | return mTargetTime < aOther.mTargetTime; |
1098 | 0 | } |
1099 | | |
1100 | | nsCOMPtr<nsIScriptTimeoutHandler> mHandler; |
1101 | | mozilla::TimeStamp mTargetTime; |
1102 | | mozilla::TimeDuration mInterval; |
1103 | | int32_t mId; |
1104 | | bool mIsInterval; |
1105 | | bool mCanceled; |
1106 | | }; |
1107 | | |
1108 | | class WorkerJSContextStats final : public JS::RuntimeStats |
1109 | | { |
1110 | | const nsCString mRtPath; |
1111 | | |
1112 | | public: |
1113 | | explicit WorkerJSContextStats(const nsACString& aRtPath) |
1114 | | : JS::RuntimeStats(JsWorkerMallocSizeOf), mRtPath(aRtPath) |
1115 | 0 | { } |
1116 | | |
1117 | | ~WorkerJSContextStats() |
1118 | 0 | { |
1119 | 0 | for (size_t i = 0; i != zoneStatsVector.length(); i++) { |
1120 | 0 | delete static_cast<xpc::ZoneStatsExtras*>(zoneStatsVector[i].extra); |
1121 | 0 | } |
1122 | 0 |
|
1123 | 0 | for (size_t i = 0; i != realmStatsVector.length(); i++) { |
1124 | 0 | delete static_cast<xpc::RealmStatsExtras*>(realmStatsVector[i].extra); |
1125 | 0 | } |
1126 | 0 | } |
1127 | | |
1128 | | const nsCString& Path() const |
1129 | 0 | { |
1130 | 0 | return mRtPath; |
1131 | 0 | } |
1132 | | |
1133 | | virtual void |
1134 | | initExtraZoneStats(JS::Zone* aZone, |
1135 | | JS::ZoneStats* aZoneStats) |
1136 | | override |
1137 | 0 | { |
1138 | 0 | MOZ_ASSERT(!aZoneStats->extra); |
1139 | 0 |
|
1140 | 0 | // ReportJSRuntimeExplicitTreeStats expects that |
1141 | 0 | // aZoneStats->extra is a xpc::ZoneStatsExtras pointer. |
1142 | 0 | xpc::ZoneStatsExtras* extras = new xpc::ZoneStatsExtras; |
1143 | 0 | extras->pathPrefix = mRtPath; |
1144 | 0 | extras->pathPrefix += nsPrintfCString("zone(0x%p)/", (void *)aZone); |
1145 | 0 |
|
1146 | 0 | MOZ_ASSERT(StartsWithExplicit(extras->pathPrefix)); |
1147 | 0 |
|
1148 | 0 | aZoneStats->extra = extras; |
1149 | 0 | } |
1150 | | |
1151 | | virtual void |
1152 | | initExtraRealmStats(JS::Handle<JS::Realm*> aRealm, |
1153 | | JS::RealmStats* aRealmStats) |
1154 | | override |
1155 | 0 | { |
1156 | 0 | MOZ_ASSERT(!aRealmStats->extra); |
1157 | 0 |
|
1158 | 0 | // ReportJSRuntimeExplicitTreeStats expects that |
1159 | 0 | // aRealmStats->extra is a xpc::RealmStatsExtras pointer. |
1160 | 0 | xpc::RealmStatsExtras* extras = new xpc::RealmStatsExtras; |
1161 | 0 |
|
1162 | 0 | // This is the |jsPathPrefix|. Each worker has exactly one realm. |
1163 | 0 | extras->jsPathPrefix.Assign(mRtPath); |
1164 | 0 | extras->jsPathPrefix += nsPrintfCString("zone(0x%p)/", |
1165 | 0 | (void *)js::GetRealmZone(aRealm)); |
1166 | 0 | extras->jsPathPrefix += NS_LITERAL_CSTRING("realm(web-worker)/"); |
1167 | 0 |
|
1168 | 0 | // This should never be used when reporting with workers (hence the "?!"). |
1169 | 0 | extras->domPathPrefix.AssignLiteral("explicit/workers/?!/"); |
1170 | 0 |
|
1171 | 0 | MOZ_ASSERT(StartsWithExplicit(extras->jsPathPrefix)); |
1172 | 0 | MOZ_ASSERT(StartsWithExplicit(extras->domPathPrefix)); |
1173 | 0 |
|
1174 | 0 | extras->location = nullptr; |
1175 | 0 |
|
1176 | 0 | aRealmStats->extra = extras; |
1177 | 0 | } |
1178 | | }; |
1179 | | |
1180 | | class WorkerPrivate::MemoryReporter final : public nsIMemoryReporter |
1181 | | { |
1182 | | NS_DECL_THREADSAFE_ISUPPORTS |
1183 | | |
1184 | | friend class WorkerPrivate; |
1185 | | |
1186 | | SharedMutex mMutex; |
1187 | | WorkerPrivate* mWorkerPrivate; |
1188 | | |
1189 | | public: |
1190 | | explicit MemoryReporter(WorkerPrivate* aWorkerPrivate) |
1191 | | : mMutex(aWorkerPrivate->mMutex), mWorkerPrivate(aWorkerPrivate) |
1192 | 0 | { |
1193 | 0 | aWorkerPrivate->AssertIsOnWorkerThread(); |
1194 | 0 | } |
1195 | | |
1196 | | NS_IMETHOD |
1197 | | CollectReports(nsIHandleReportCallback* aHandleReport, |
1198 | | nsISupports* aData, bool aAnonymize) override; |
1199 | | |
1200 | | private: |
1201 | | class FinishCollectRunnable; |
1202 | | |
1203 | | class CollectReportsRunnable final : public MainThreadWorkerControlRunnable |
1204 | | { |
1205 | | RefPtr<FinishCollectRunnable> mFinishCollectRunnable; |
1206 | | const bool mAnonymize; |
1207 | | |
1208 | | public: |
1209 | | CollectReportsRunnable( |
1210 | | WorkerPrivate* aWorkerPrivate, |
1211 | | nsIHandleReportCallback* aHandleReport, |
1212 | | nsISupports* aHandlerData, |
1213 | | bool aAnonymize, |
1214 | | const nsACString& aPath); |
1215 | | |
1216 | | private: |
1217 | | bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override; |
1218 | | |
1219 | | ~CollectReportsRunnable() |
1220 | 0 | { |
1221 | 0 | if (NS_IsMainThread()) { |
1222 | 0 | mFinishCollectRunnable->Run(); |
1223 | 0 | return; |
1224 | 0 | } |
1225 | 0 | |
1226 | 0 | WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); |
1227 | 0 | MOZ_ASSERT(workerPrivate); |
1228 | 0 | MOZ_ALWAYS_SUCCEEDS( |
1229 | 0 | workerPrivate->DispatchToMainThread(mFinishCollectRunnable.forget())); |
1230 | 0 | } |
1231 | | }; |
1232 | | |
1233 | | class FinishCollectRunnable final : public Runnable |
1234 | | { |
1235 | | nsCOMPtr<nsIHandleReportCallback> mHandleReport; |
1236 | | nsCOMPtr<nsISupports> mHandlerData; |
1237 | | size_t mPerformanceUserEntries; |
1238 | | size_t mPerformanceResourceEntries; |
1239 | | const bool mAnonymize; |
1240 | | bool mSuccess; |
1241 | | |
1242 | | public: |
1243 | | WorkerJSContextStats mCxStats; |
1244 | | |
1245 | | explicit FinishCollectRunnable( |
1246 | | nsIHandleReportCallback* aHandleReport, |
1247 | | nsISupports* aHandlerData, |
1248 | | bool aAnonymize, |
1249 | | const nsACString& aPath); |
1250 | | |
1251 | | NS_IMETHOD Run() override; |
1252 | | |
1253 | | void SetPerformanceSizes(size_t userEntries, size_t resourceEntries) |
1254 | 0 | { |
1255 | 0 | mPerformanceUserEntries = userEntries; |
1256 | 0 | mPerformanceResourceEntries = resourceEntries; |
1257 | 0 | } |
1258 | | |
1259 | | void SetSuccess(bool success) |
1260 | 0 | { |
1261 | 0 | mSuccess = success; |
1262 | 0 | } |
1263 | | |
1264 | | private: |
1265 | | ~FinishCollectRunnable() |
1266 | 0 | { |
1267 | 0 | // mHandleReport and mHandlerData are released on the main thread. |
1268 | 0 | AssertIsOnMainThread(); |
1269 | 0 | } |
1270 | | |
1271 | | FinishCollectRunnable(const FinishCollectRunnable&) = delete; |
1272 | | FinishCollectRunnable& operator=(const FinishCollectRunnable&) = delete; |
1273 | | FinishCollectRunnable& operator=(const FinishCollectRunnable&&) = delete; |
1274 | | }; |
1275 | | |
1276 | | ~MemoryReporter() |
1277 | 0 | { |
1278 | 0 | } |
1279 | | |
1280 | | void |
1281 | | Disable() |
1282 | 0 | { |
1283 | 0 | // Called from WorkerPrivate::DisableMemoryReporter. |
1284 | 0 | mMutex.AssertCurrentThreadOwns(); |
1285 | 0 |
|
1286 | 0 | NS_ASSERTION(mWorkerPrivate, "Disabled more than once!"); |
1287 | 0 | mWorkerPrivate = nullptr; |
1288 | 0 | } |
1289 | | }; |
1290 | | |
1291 | | NS_IMPL_ISUPPORTS(WorkerPrivate::MemoryReporter, nsIMemoryReporter) |
1292 | | |
1293 | | NS_IMETHODIMP |
1294 | | WorkerPrivate::MemoryReporter::CollectReports(nsIHandleReportCallback* aHandleReport, |
1295 | | nsISupports* aData, |
1296 | | bool aAnonymize) |
1297 | 0 | { |
1298 | 0 | AssertIsOnMainThread(); |
1299 | 0 |
|
1300 | 0 | RefPtr<CollectReportsRunnable> runnable; |
1301 | 0 |
|
1302 | 0 | { |
1303 | 0 | MutexAutoLock lock(mMutex); |
1304 | 0 |
|
1305 | 0 | if (!mWorkerPrivate) { |
1306 | 0 | // This will effectively report 0 memory. |
1307 | 0 | nsCOMPtr<nsIMemoryReporterManager> manager = |
1308 | 0 | do_GetService("@mozilla.org/memory-reporter-manager;1"); |
1309 | 0 | if (manager) { |
1310 | 0 | manager->EndReport(); |
1311 | 0 | } |
1312 | 0 | return NS_OK; |
1313 | 0 | } |
1314 | 0 |
|
1315 | 0 | nsAutoCString path; |
1316 | 0 | path.AppendLiteral("explicit/workers/workers("); |
1317 | 0 | if (aAnonymize && !mWorkerPrivate->Domain().IsEmpty()) { |
1318 | 0 | path.AppendLiteral("<anonymized-domain>)/worker(<anonymized-url>"); |
1319 | 0 | } else { |
1320 | 0 | nsAutoCString escapedDomain(mWorkerPrivate->Domain()); |
1321 | 0 | if (escapedDomain.IsEmpty()) { |
1322 | 0 | escapedDomain += "chrome"; |
1323 | 0 | } else { |
1324 | 0 | escapedDomain.ReplaceChar('/', '\\'); |
1325 | 0 | } |
1326 | 0 | path.Append(escapedDomain); |
1327 | 0 | path.AppendLiteral(")/worker("); |
1328 | 0 | NS_ConvertUTF16toUTF8 escapedURL(mWorkerPrivate->ScriptURL()); |
1329 | 0 | escapedURL.ReplaceChar('/', '\\'); |
1330 | 0 | path.Append(escapedURL); |
1331 | 0 | } |
1332 | 0 | path.AppendPrintf(", 0x%p)/", static_cast<void*>(mWorkerPrivate)); |
1333 | 0 |
|
1334 | 0 | runnable = |
1335 | 0 | new CollectReportsRunnable(mWorkerPrivate, aHandleReport, aData, aAnonymize, path); |
1336 | 0 | } |
1337 | 0 |
|
1338 | 0 | if (!runnable->Dispatch()) { |
1339 | 0 | return NS_ERROR_UNEXPECTED; |
1340 | 0 | } |
1341 | 0 | |
1342 | 0 | return NS_OK; |
1343 | 0 | } |
1344 | | |
1345 | | WorkerPrivate::MemoryReporter::CollectReportsRunnable::CollectReportsRunnable( |
1346 | | WorkerPrivate* aWorkerPrivate, |
1347 | | nsIHandleReportCallback* aHandleReport, |
1348 | | nsISupports* aHandlerData, |
1349 | | bool aAnonymize, |
1350 | | const nsACString& aPath) |
1351 | | : MainThreadWorkerControlRunnable(aWorkerPrivate), |
1352 | | mFinishCollectRunnable( |
1353 | | new FinishCollectRunnable(aHandleReport, aHandlerData, aAnonymize, aPath)), |
1354 | | mAnonymize(aAnonymize) |
1355 | 0 | { } |
1356 | | |
1357 | | bool |
1358 | | WorkerPrivate::MemoryReporter::CollectReportsRunnable::WorkerRun(JSContext* aCx, |
1359 | | WorkerPrivate* aWorkerPrivate) |
1360 | 0 | { |
1361 | 0 | aWorkerPrivate->AssertIsOnWorkerThread(); |
1362 | 0 |
|
1363 | 0 | RefPtr<WorkerGlobalScope> scope = aWorkerPrivate->GlobalScope(); |
1364 | 0 | RefPtr<Performance> performance = scope ? scope->GetPerformanceIfExists() |
1365 | 0 | : nullptr; |
1366 | 0 | if (performance) { |
1367 | 0 | size_t userEntries = performance->SizeOfUserEntries(JsWorkerMallocSizeOf); |
1368 | 0 | size_t resourceEntries = |
1369 | 0 | performance->SizeOfResourceEntries(JsWorkerMallocSizeOf); |
1370 | 0 | mFinishCollectRunnable->SetPerformanceSizes(userEntries, resourceEntries); |
1371 | 0 | } |
1372 | 0 |
|
1373 | 0 | mFinishCollectRunnable->SetSuccess( |
1374 | 0 | aWorkerPrivate->CollectRuntimeStats(&mFinishCollectRunnable->mCxStats, mAnonymize)); |
1375 | 0 |
|
1376 | 0 | return true; |
1377 | 0 | } |
1378 | | |
1379 | | WorkerPrivate::MemoryReporter::FinishCollectRunnable::FinishCollectRunnable( |
1380 | | nsIHandleReportCallback* aHandleReport, |
1381 | | nsISupports* aHandlerData, |
1382 | | bool aAnonymize, |
1383 | | const nsACString& aPath) |
1384 | | : mozilla::Runnable("dom::WorkerPrivate::MemoryReporter::FinishCollectRunnable") |
1385 | | , mHandleReport(aHandleReport) |
1386 | | , mHandlerData(aHandlerData) |
1387 | | , mPerformanceUserEntries(0) |
1388 | | , mPerformanceResourceEntries(0) |
1389 | | , mAnonymize(aAnonymize) |
1390 | | , mSuccess(false) |
1391 | | , mCxStats(aPath) |
1392 | 0 | { } |
1393 | | |
1394 | | NS_IMETHODIMP |
1395 | | WorkerPrivate::MemoryReporter::FinishCollectRunnable::Run() |
1396 | 0 | { |
1397 | 0 | AssertIsOnMainThread(); |
1398 | 0 |
|
1399 | 0 | nsCOMPtr<nsIMemoryReporterManager> manager = |
1400 | 0 | do_GetService("@mozilla.org/memory-reporter-manager;1"); |
1401 | 0 |
|
1402 | 0 | if (!manager) |
1403 | 0 | return NS_OK; |
1404 | 0 | |
1405 | 0 | if (mSuccess) { |
1406 | 0 | xpc::ReportJSRuntimeExplicitTreeStats(mCxStats, mCxStats.Path(), |
1407 | 0 | mHandleReport, mHandlerData, |
1408 | 0 | mAnonymize); |
1409 | 0 |
|
1410 | 0 | if (mPerformanceUserEntries) { |
1411 | 0 | nsCString path = mCxStats.Path(); |
1412 | 0 | path.AppendLiteral("dom/performance/user-entries"); |
1413 | 0 | mHandleReport->Callback(EmptyCString(), path, |
1414 | 0 | nsIMemoryReporter::KIND_HEAP, |
1415 | 0 | nsIMemoryReporter::UNITS_BYTES, |
1416 | 0 | mPerformanceUserEntries, |
1417 | 0 | NS_LITERAL_CSTRING("Memory used for performance user entries."), |
1418 | 0 | mHandlerData); |
1419 | 0 | } |
1420 | 0 |
|
1421 | 0 | if (mPerformanceResourceEntries) { |
1422 | 0 | nsCString path = mCxStats.Path(); |
1423 | 0 | path.AppendLiteral("dom/performance/resource-entries"); |
1424 | 0 | mHandleReport->Callback(EmptyCString(), path, |
1425 | 0 | nsIMemoryReporter::KIND_HEAP, |
1426 | 0 | nsIMemoryReporter::UNITS_BYTES, |
1427 | 0 | mPerformanceResourceEntries, |
1428 | 0 | NS_LITERAL_CSTRING("Memory used for performance resource entries."), |
1429 | 0 | mHandlerData); |
1430 | 0 | } |
1431 | 0 | } |
1432 | 0 |
|
1433 | 0 | manager->EndReport(); |
1434 | 0 |
|
1435 | 0 | return NS_OK; |
1436 | 0 | } |
1437 | | |
1438 | | WorkerPrivate::SyncLoopInfo::SyncLoopInfo(EventTarget* aEventTarget) |
1439 | | : mEventTarget(aEventTarget), mCompleted(false), mResult(false) |
1440 | | #ifdef DEBUG |
1441 | | , mHasRun(false) |
1442 | | #endif |
1443 | 0 | { |
1444 | 0 | } |
1445 | | |
1446 | | nsIDocument* |
1447 | | WorkerPrivate::GetDocument() const |
1448 | 0 | { |
1449 | 0 | AssertIsOnMainThread(); |
1450 | 0 | if (mLoadInfo.mWindow) { |
1451 | 0 | return mLoadInfo.mWindow->GetExtantDoc(); |
1452 | 0 | } |
1453 | 0 | // if we don't have a document, we should query the document |
1454 | 0 | // from the parent in case of a nested worker |
1455 | 0 | WorkerPrivate* parent = mParent; |
1456 | 0 | while (parent) { |
1457 | 0 | if (parent->mLoadInfo.mWindow) { |
1458 | 0 | return parent->mLoadInfo.mWindow->GetExtantDoc(); |
1459 | 0 | } |
1460 | 0 | parent = parent->GetParent(); |
1461 | 0 | } |
1462 | 0 | // couldn't query a document, give up and return nullptr |
1463 | 0 | return nullptr; |
1464 | 0 | } |
1465 | | |
1466 | | void |
1467 | | WorkerPrivate::SetCSP(nsIContentSecurityPolicy* aCSP) |
1468 | 0 | { |
1469 | 0 | AssertIsOnMainThread(); |
1470 | 0 | if (!aCSP) { |
1471 | 0 | return; |
1472 | 0 | } |
1473 | 0 | aCSP->EnsureEventTarget(mMainThreadEventTarget); |
1474 | 0 | aCSP->SetEventListener(mCSPEventListener); |
1475 | 0 |
|
1476 | 0 | mLoadInfo.mCSP = aCSP; |
1477 | 0 | } |
1478 | | |
1479 | | nsresult |
1480 | | WorkerPrivate::SetCSPFromHeaderValues(const nsACString& aCSPHeaderValue, |
1481 | | const nsACString& aCSPReportOnlyHeaderValue) |
1482 | 0 | { |
1483 | 0 | AssertIsOnMainThread(); |
1484 | 0 | MOZ_DIAGNOSTIC_ASSERT(!mLoadInfo.mCSP); |
1485 | 0 |
|
1486 | 0 | NS_ConvertASCIItoUTF16 cspHeaderValue(aCSPHeaderValue); |
1487 | 0 | NS_ConvertASCIItoUTF16 cspROHeaderValue(aCSPReportOnlyHeaderValue); |
1488 | 0 |
|
1489 | 0 | nsCOMPtr<nsIContentSecurityPolicy> csp; |
1490 | 0 | nsresult rv = mLoadInfo.mPrincipal->EnsureCSP(nullptr, getter_AddRefs(csp)); |
1491 | 0 | if (!csp) { |
1492 | 0 | return NS_OK; |
1493 | 0 | } |
1494 | 0 | |
1495 | 0 | csp->EnsureEventTarget(mMainThreadEventTarget); |
1496 | 0 | csp->SetEventListener(mCSPEventListener); |
1497 | 0 |
|
1498 | 0 | // If there's a CSP header, apply it. |
1499 | 0 | if (!cspHeaderValue.IsEmpty()) { |
1500 | 0 | rv = CSP_AppendCSPFromHeader(csp, cspHeaderValue, false); |
1501 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1502 | 0 | } |
1503 | 0 | // If there's a report-only CSP header, apply it. |
1504 | 0 | if (!cspROHeaderValue.IsEmpty()) { |
1505 | 0 | rv = CSP_AppendCSPFromHeader(csp, cspROHeaderValue, true); |
1506 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1507 | 0 | } |
1508 | 0 |
|
1509 | 0 | // Set evalAllowed, default value is set in GetAllowsEval |
1510 | 0 | bool evalAllowed = false; |
1511 | 0 | bool reportEvalViolations = false; |
1512 | 0 | rv = csp->GetAllowsEval(&reportEvalViolations, &evalAllowed); |
1513 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1514 | 0 |
|
1515 | 0 | mLoadInfo.mCSP = csp; |
1516 | 0 | mLoadInfo.mEvalAllowed = evalAllowed; |
1517 | 0 | mLoadInfo.mReportCSPViolations = reportEvalViolations; |
1518 | 0 |
|
1519 | 0 | return NS_OK; |
1520 | 0 | } |
1521 | | |
1522 | | void |
1523 | | WorkerPrivate::SetReferrerPolicyFromHeaderValue(const nsACString& aReferrerPolicyHeaderValue) |
1524 | 0 | { |
1525 | 0 | NS_ConvertUTF8toUTF16 headerValue(aReferrerPolicyHeaderValue); |
1526 | 0 |
|
1527 | 0 | if (headerValue.IsEmpty()) { |
1528 | 0 | return; |
1529 | 0 | } |
1530 | 0 | |
1531 | 0 | net::ReferrerPolicy policy = |
1532 | 0 | nsContentUtils::GetReferrerPolicyFromHeader(headerValue); |
1533 | 0 | if (policy == net::RP_Unset) { |
1534 | 0 | return; |
1535 | 0 | } |
1536 | 0 | |
1537 | 0 | SetReferrerPolicy(policy); |
1538 | 0 | } |
1539 | | |
1540 | | void |
1541 | | WorkerPrivate::Traverse(nsCycleCollectionTraversalCallback& aCb) |
1542 | 0 | { |
1543 | 0 | AssertIsOnParentThread(); |
1544 | 0 |
|
1545 | 0 | // The WorkerPrivate::mParentEventTargetRef has a reference to the exposed |
1546 | 0 | // Worker object, which is really held by the worker thread. We traverse this |
1547 | 0 | // reference if and only if our busy count is zero and we have not released |
1548 | 0 | // the main thread reference. We do not unlink it. This allows the CC to |
1549 | 0 | // break cycles involving the Worker and begin shutting it down (which does |
1550 | 0 | // happen in unlink) but ensures that the WorkerPrivate won't be deleted |
1551 | 0 | // before we're done shutting down the thread. |
1552 | 0 | if (!mBusyCount && !mMainThreadObjectsForgotten) { |
1553 | 0 | nsCycleCollectionTraversalCallback& cb = aCb; |
1554 | 0 | WorkerPrivate* tmp = this; |
1555 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParentEventTargetRef); |
1556 | 0 | } |
1557 | 0 | } |
1558 | | |
1559 | | nsresult |
1560 | | WorkerPrivate::Dispatch(already_AddRefed<WorkerRunnable> aRunnable, |
1561 | | nsIEventTarget* aSyncLoopTarget) |
1562 | 0 | { |
1563 | 0 | // May be called on any thread! |
1564 | 0 | RefPtr<WorkerRunnable> runnable(aRunnable); |
1565 | 0 |
|
1566 | 0 | { |
1567 | 0 | MutexAutoLock lock(mMutex); |
1568 | 0 |
|
1569 | 0 | MOZ_ASSERT_IF(aSyncLoopTarget, mThread); |
1570 | 0 |
|
1571 | 0 | if (!mThread) { |
1572 | 0 | if (ParentStatus() == Pending || mStatus == Pending) { |
1573 | 0 | mPreStartRunnables.AppendElement(runnable); |
1574 | 0 | return NS_OK; |
1575 | 0 | } |
1576 | 0 | |
1577 | 0 | NS_WARNING("Using a worker event target after the thread has already" |
1578 | 0 | "been released!"); |
1579 | 0 | return NS_ERROR_UNEXPECTED; |
1580 | 0 | } |
1581 | 0 | |
1582 | 0 | if (mStatus == Dead || |
1583 | 0 | (!aSyncLoopTarget && ParentStatus() > Running)) { |
1584 | 0 | NS_WARNING("A runnable was posted to a worker that is already shutting " |
1585 | 0 | "down!"); |
1586 | 0 | return NS_ERROR_UNEXPECTED; |
1587 | 0 | } |
1588 | 0 |
|
1589 | 0 | nsresult rv; |
1590 | 0 | if (aSyncLoopTarget) { |
1591 | 0 | rv = aSyncLoopTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); |
1592 | 0 | } else { |
1593 | 0 | rv = mThread->DispatchAnyThread(WorkerThreadFriendKey(), runnable.forget()); |
1594 | 0 | } |
1595 | 0 |
|
1596 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1597 | 0 | return rv; |
1598 | 0 | } |
1599 | 0 | |
1600 | 0 | mCondVar.Notify(); |
1601 | 0 | } |
1602 | 0 |
|
1603 | 0 | return NS_OK; |
1604 | 0 | } |
1605 | | |
1606 | | void |
1607 | | WorkerPrivate::EnableDebugger() |
1608 | 0 | { |
1609 | 0 | AssertIsOnParentThread(); |
1610 | 0 |
|
1611 | 0 | if (NS_FAILED(RegisterWorkerDebugger(this))) { |
1612 | 0 | NS_WARNING("Failed to register worker debugger!"); |
1613 | 0 | return; |
1614 | 0 | } |
1615 | 0 | } |
1616 | | |
1617 | | void |
1618 | | WorkerPrivate::DisableDebugger() |
1619 | 0 | { |
1620 | 0 | AssertIsOnParentThread(); |
1621 | 0 |
|
1622 | 0 | if (NS_FAILED(UnregisterWorkerDebugger(this))) { |
1623 | 0 | NS_WARNING("Failed to unregister worker debugger!"); |
1624 | 0 | } |
1625 | 0 | } |
1626 | | |
1627 | | nsresult |
1628 | | WorkerPrivate::DispatchControlRunnable(already_AddRefed<WorkerControlRunnable> aWorkerControlRunnable) |
1629 | 0 | { |
1630 | 0 | // May be called on any thread! |
1631 | 0 | RefPtr<WorkerControlRunnable> runnable(aWorkerControlRunnable); |
1632 | 0 | MOZ_ASSERT(runnable); |
1633 | 0 |
|
1634 | 0 | { |
1635 | 0 | MutexAutoLock lock(mMutex); |
1636 | 0 |
|
1637 | 0 | if (mStatus == Dead) { |
1638 | 0 | return NS_ERROR_UNEXPECTED; |
1639 | 0 | } |
1640 | 0 | |
1641 | 0 | // Transfer ownership to the control queue. |
1642 | 0 | mControlQueue.Push(runnable.forget().take()); |
1643 | 0 |
|
1644 | 0 | if (JSContext* cx = mJSContext) { |
1645 | 0 | MOZ_ASSERT(mThread); |
1646 | 0 | JS_RequestInterruptCallback(cx); |
1647 | 0 | } |
1648 | 0 |
|
1649 | 0 | mCondVar.Notify(); |
1650 | 0 | } |
1651 | 0 |
|
1652 | 0 | return NS_OK; |
1653 | 0 | } |
1654 | | |
1655 | | nsresult |
1656 | | WorkerPrivate::DispatchDebuggerRunnable(already_AddRefed<WorkerRunnable> aDebuggerRunnable) |
1657 | 0 | { |
1658 | 0 | // May be called on any thread! |
1659 | 0 |
|
1660 | 0 | RefPtr<WorkerRunnable> runnable(aDebuggerRunnable); |
1661 | 0 |
|
1662 | 0 | MOZ_ASSERT(runnable); |
1663 | 0 |
|
1664 | 0 | { |
1665 | 0 | MutexAutoLock lock(mMutex); |
1666 | 0 |
|
1667 | 0 | if (mStatus == Dead) { |
1668 | 0 | NS_WARNING("A debugger runnable was posted to a worker that is already " |
1669 | 0 | "shutting down!"); |
1670 | 0 | return NS_ERROR_UNEXPECTED; |
1671 | 0 | } |
1672 | 0 |
|
1673 | 0 | // Transfer ownership to the debugger queue. |
1674 | 0 | mDebuggerQueue.Push(runnable.forget().take()); |
1675 | 0 |
|
1676 | 0 | mCondVar.Notify(); |
1677 | 0 | } |
1678 | 0 |
|
1679 | 0 | return NS_OK; |
1680 | 0 | } |
1681 | | |
1682 | | already_AddRefed<WorkerRunnable> |
1683 | | WorkerPrivate::MaybeWrapAsWorkerRunnable(already_AddRefed<nsIRunnable> aRunnable) |
1684 | 0 | { |
1685 | 0 | // May be called on any thread! |
1686 | 0 |
|
1687 | 0 | nsCOMPtr<nsIRunnable> runnable(aRunnable); |
1688 | 0 | MOZ_ASSERT(runnable); |
1689 | 0 |
|
1690 | 0 | RefPtr<WorkerRunnable> workerRunnable = |
1691 | 0 | WorkerRunnable::FromRunnable(runnable); |
1692 | 0 | if (workerRunnable) { |
1693 | 0 | return workerRunnable.forget(); |
1694 | 0 | } |
1695 | 0 | |
1696 | 0 | nsCOMPtr<nsICancelableRunnable> cancelable = do_QueryInterface(runnable); |
1697 | 0 | if (!cancelable) { |
1698 | 0 | MOZ_CRASH("All runnables destined for a worker thread must be cancelable!"); |
1699 | 0 | } |
1700 | 0 |
|
1701 | 0 | workerRunnable = new ExternalRunnableWrapper(this, runnable); |
1702 | 0 | return workerRunnable.forget(); |
1703 | 0 | } |
1704 | | |
1705 | | bool |
1706 | | WorkerPrivate::Start() |
1707 | 0 | { |
1708 | 0 | // May be called on any thread! |
1709 | 0 | { |
1710 | 0 | MutexAutoLock lock(mMutex); |
1711 | 0 | NS_ASSERTION(mParentStatus != Running, "How can this be?!"); |
1712 | 0 |
|
1713 | 0 | if (mParentStatus == Pending) { |
1714 | 0 | mParentStatus = Running; |
1715 | 0 | return true; |
1716 | 0 | } |
1717 | 0 | } |
1718 | 0 | |
1719 | 0 | return false; |
1720 | 0 | } |
1721 | | |
1722 | | // aCx is null when called from the finalizer |
1723 | | bool |
1724 | | WorkerPrivate::Notify(WorkerStatus aStatus) |
1725 | 0 | { |
1726 | 0 | AssertIsOnParentThread(); |
1727 | 0 |
|
1728 | 0 | bool pending; |
1729 | 0 | { |
1730 | 0 | MutexAutoLock lock(mMutex); |
1731 | 0 |
|
1732 | 0 | if (mParentStatus >= aStatus) { |
1733 | 0 | return true; |
1734 | 0 | } |
1735 | 0 | |
1736 | 0 | pending = mParentStatus == Pending; |
1737 | 0 | mParentStatus = aStatus; |
1738 | 0 | } |
1739 | 0 |
|
1740 | 0 | if (IsSharedWorker()) { |
1741 | 0 | RuntimeService* runtime = RuntimeService::GetService(); |
1742 | 0 | MOZ_ASSERT(runtime); |
1743 | 0 |
|
1744 | 0 | runtime->ForgetSharedWorker(this); |
1745 | 0 | } |
1746 | 0 |
|
1747 | 0 | if (pending) { |
1748 | | #ifdef DEBUG |
1749 | | { |
1750 | | // Fake a thread here just so that our assertions don't go off for no |
1751 | | // reason. |
1752 | | nsIThread* currentThread = NS_GetCurrentThread(); |
1753 | | MOZ_ASSERT(currentThread); |
1754 | | |
1755 | | MOZ_ASSERT(!mPRThread); |
1756 | | mPRThread = PRThreadFromThread(currentThread); |
1757 | | MOZ_ASSERT(mPRThread); |
1758 | | } |
1759 | | #endif |
1760 | |
|
1761 | 0 | // Worker never got a chance to run, go ahead and delete it. |
1762 | 0 | ScheduleDeletion(WorkerPrivate::WorkerNeverRan); |
1763 | 0 | return true; |
1764 | 0 | } |
1765 | 0 |
|
1766 | 0 | NS_ASSERTION(aStatus != Canceling || mQueuedRunnables.IsEmpty(), |
1767 | 0 | "Shouldn't have anything queued!"); |
1768 | 0 |
|
1769 | 0 | // Anything queued will be discarded. |
1770 | 0 | mQueuedRunnables.Clear(); |
1771 | 0 |
|
1772 | 0 | // No Canceling timeout is needed. |
1773 | 0 | if (mCancelingTimer) { |
1774 | 0 | mCancelingTimer->Cancel(); |
1775 | 0 | mCancelingTimer = nullptr; |
1776 | 0 | } |
1777 | 0 |
|
1778 | 0 | RefPtr<NotifyRunnable> runnable = new NotifyRunnable(this, aStatus); |
1779 | 0 | return runnable->Dispatch(); |
1780 | 0 | } |
1781 | | |
1782 | | bool |
1783 | | WorkerPrivate::Freeze(nsPIDOMWindowInner* aWindow) |
1784 | 0 | { |
1785 | 0 | AssertIsOnParentThread(); |
1786 | 0 |
|
1787 | 0 | // Shared workers are only frozen if all of their owning documents are |
1788 | 0 | // frozen. It can happen that mSharedWorkers is empty but this thread has |
1789 | 0 | // not been unregistered yet. |
1790 | 0 | if ((IsSharedWorker() || IsServiceWorker()) && !mSharedWorkers.IsEmpty()) { |
1791 | 0 | AssertIsOnMainThread(); |
1792 | 0 |
|
1793 | 0 | bool allFrozen = true; |
1794 | 0 |
|
1795 | 0 | for (uint32_t i = 0; i < mSharedWorkers.Length(); ++i) { |
1796 | 0 | if (aWindow && mSharedWorkers[i]->GetOwner() == aWindow) { |
1797 | 0 | // Calling Freeze() may change the refcount, ensure that the worker |
1798 | 0 | // outlives this call. |
1799 | 0 | RefPtr<SharedWorker> kungFuDeathGrip = mSharedWorkers[i]; |
1800 | 0 |
|
1801 | 0 | kungFuDeathGrip->Freeze(); |
1802 | 0 | } else { |
1803 | 0 | MOZ_ASSERT_IF(mSharedWorkers[i]->GetOwner() && aWindow, |
1804 | 0 | !SameCOMIdentity(mSharedWorkers[i]->GetOwner(), aWindow)); |
1805 | 0 | if (!mSharedWorkers[i]->IsFrozen()) { |
1806 | 0 | allFrozen = false; |
1807 | 0 | } |
1808 | 0 | } |
1809 | 0 | } |
1810 | 0 |
|
1811 | 0 | if (!allFrozen || mParentFrozen) { |
1812 | 0 | return true; |
1813 | 0 | } |
1814 | 0 | } |
1815 | 0 | |
1816 | 0 | mParentFrozen = true; |
1817 | 0 |
|
1818 | 0 | { |
1819 | 0 | MutexAutoLock lock(mMutex); |
1820 | 0 |
|
1821 | 0 | if (mParentStatus >= Canceling) { |
1822 | 0 | return true; |
1823 | 0 | } |
1824 | 0 | } |
1825 | 0 | |
1826 | 0 | DisableDebugger(); |
1827 | 0 |
|
1828 | 0 | RefPtr<FreezeRunnable> runnable = new FreezeRunnable(this); |
1829 | 0 | if (!runnable->Dispatch()) { |
1830 | 0 | return false; |
1831 | 0 | } |
1832 | 0 | |
1833 | 0 | return true; |
1834 | 0 | } |
1835 | | |
1836 | | bool |
1837 | | WorkerPrivate::Thaw(nsPIDOMWindowInner* aWindow) |
1838 | 0 | { |
1839 | 0 | AssertIsOnParentThread(); |
1840 | 0 |
|
1841 | 0 | // Shared workers are resumed if any of their owning documents are thawed. |
1842 | 0 | // It can happen that mSharedWorkers is empty but this thread has not been |
1843 | 0 | // unregistered yet. |
1844 | 0 | if ((IsSharedWorker() || IsServiceWorker()) && !mSharedWorkers.IsEmpty()) { |
1845 | 0 | AssertIsOnMainThread(); |
1846 | 0 |
|
1847 | 0 | bool anyRunning = false; |
1848 | 0 |
|
1849 | 0 | for (uint32_t i = 0; i < mSharedWorkers.Length(); ++i) { |
1850 | 0 | if (aWindow && mSharedWorkers[i]->GetOwner() == aWindow) { |
1851 | 0 | // Calling Thaw() may change the refcount, ensure that the worker |
1852 | 0 | // outlives this call. |
1853 | 0 | RefPtr<SharedWorker> kungFuDeathGrip = mSharedWorkers[i]; |
1854 | 0 |
|
1855 | 0 | kungFuDeathGrip->Thaw(); |
1856 | 0 | anyRunning = true; |
1857 | 0 | } else { |
1858 | 0 | MOZ_ASSERT_IF(mSharedWorkers[i]->GetOwner() && aWindow, |
1859 | 0 | !SameCOMIdentity(mSharedWorkers[i]->GetOwner(), aWindow)); |
1860 | 0 | if (!mSharedWorkers[i]->IsFrozen()) { |
1861 | 0 | anyRunning = true; |
1862 | 0 | } |
1863 | 0 | } |
1864 | 0 | } |
1865 | 0 |
|
1866 | 0 | if (!anyRunning || !mParentFrozen) { |
1867 | 0 | return true; |
1868 | 0 | } |
1869 | 0 | } |
1870 | 0 | |
1871 | 0 | MOZ_ASSERT(mParentFrozen); |
1872 | 0 |
|
1873 | 0 | mParentFrozen = false; |
1874 | 0 |
|
1875 | 0 | { |
1876 | 0 | MutexAutoLock lock(mMutex); |
1877 | 0 |
|
1878 | 0 | if (mParentStatus >= Canceling) { |
1879 | 0 | return true; |
1880 | 0 | } |
1881 | 0 | } |
1882 | 0 | |
1883 | 0 | EnableDebugger(); |
1884 | 0 |
|
1885 | 0 | // Execute queued runnables before waking up the worker, otherwise the worker |
1886 | 0 | // could post new messages before we run those that have been queued. |
1887 | 0 | if (!IsParentWindowPaused() && !mQueuedRunnables.IsEmpty()) { |
1888 | 0 | MOZ_ASSERT(IsDedicatedWorker()); |
1889 | 0 |
|
1890 | 0 | nsTArray<nsCOMPtr<nsIRunnable>> runnables; |
1891 | 0 | mQueuedRunnables.SwapElements(runnables); |
1892 | 0 |
|
1893 | 0 | for (uint32_t index = 0; index < runnables.Length(); index++) { |
1894 | 0 | runnables[index]->Run(); |
1895 | 0 | } |
1896 | 0 | } |
1897 | 0 |
|
1898 | 0 | RefPtr<ThawRunnable> runnable = new ThawRunnable(this); |
1899 | 0 | if (!runnable->Dispatch()) { |
1900 | 0 | return false; |
1901 | 0 | } |
1902 | 0 | |
1903 | 0 | return true; |
1904 | 0 | } |
1905 | | |
1906 | | void |
1907 | | WorkerPrivate::ParentWindowPaused() |
1908 | 0 | { |
1909 | 0 | AssertIsOnMainThread(); |
1910 | 0 | MOZ_ASSERT_IF(IsDedicatedWorker(), mParentWindowPausedDepth == 0); |
1911 | 0 | mParentWindowPausedDepth += 1; |
1912 | 0 | } |
1913 | | |
1914 | | void |
1915 | | WorkerPrivate::ParentWindowResumed() |
1916 | 0 | { |
1917 | 0 | AssertIsOnMainThread(); |
1918 | 0 |
|
1919 | 0 | MOZ_ASSERT(mParentWindowPausedDepth > 0); |
1920 | 0 | MOZ_ASSERT_IF(IsDedicatedWorker(), mParentWindowPausedDepth == 1); |
1921 | 0 | mParentWindowPausedDepth -= 1; |
1922 | 0 | if (mParentWindowPausedDepth > 0) { |
1923 | 0 | return; |
1924 | 0 | } |
1925 | 0 | |
1926 | 0 | { |
1927 | 0 | MutexAutoLock lock(mMutex); |
1928 | 0 |
|
1929 | 0 | if (mParentStatus >= Canceling) { |
1930 | 0 | return; |
1931 | 0 | } |
1932 | 0 | } |
1933 | 0 | |
1934 | 0 | // Execute queued runnables before waking up, otherwise the worker could post |
1935 | 0 | // new messages before we run those that have been queued. |
1936 | 0 | if (!IsFrozen() && !mQueuedRunnables.IsEmpty()) { |
1937 | 0 | MOZ_ASSERT(IsDedicatedWorker()); |
1938 | 0 |
|
1939 | 0 | nsTArray<nsCOMPtr<nsIRunnable>> runnables; |
1940 | 0 | mQueuedRunnables.SwapElements(runnables); |
1941 | 0 |
|
1942 | 0 | for (uint32_t index = 0; index < runnables.Length(); index++) { |
1943 | 0 | runnables[index]->Run(); |
1944 | 0 | } |
1945 | 0 | } |
1946 | 0 | } |
1947 | | |
1948 | | void |
1949 | | WorkerPrivate::PropagateFirstPartyStorageAccessGranted() |
1950 | 0 | { |
1951 | 0 | AssertIsOnParentThread(); |
1952 | 0 |
|
1953 | 0 | { |
1954 | 0 | MutexAutoLock lock(mMutex); |
1955 | 0 |
|
1956 | 0 | if (mParentStatus >= Canceling) { |
1957 | 0 | return; |
1958 | 0 | } |
1959 | 0 | } |
1960 | 0 | |
1961 | 0 | RefPtr<PropagateFirstPartyStorageAccessGrantedRunnable> runnable = |
1962 | 0 | new PropagateFirstPartyStorageAccessGrantedRunnable(this); |
1963 | 0 | Unused << NS_WARN_IF(!runnable->Dispatch()); |
1964 | 0 | } |
1965 | | |
1966 | | bool |
1967 | | WorkerPrivate::Close() |
1968 | 0 | { |
1969 | 0 | mMutex.AssertCurrentThreadOwns(); |
1970 | 0 | if (mParentStatus < Closing) { |
1971 | 0 | mParentStatus = Closing; |
1972 | 0 | } |
1973 | 0 |
|
1974 | 0 | return true; |
1975 | 0 | } |
1976 | | |
1977 | | bool |
1978 | | WorkerPrivate::ModifyBusyCount(bool aIncrease) |
1979 | 0 | { |
1980 | 0 | AssertIsOnParentThread(); |
1981 | 0 |
|
1982 | 0 | NS_ASSERTION(aIncrease || mBusyCount, "Mismatched busy count mods!"); |
1983 | 0 |
|
1984 | 0 | if (aIncrease) { |
1985 | 0 | mBusyCount++; |
1986 | 0 | return true; |
1987 | 0 | } |
1988 | 0 | |
1989 | 0 | if (--mBusyCount == 0) { |
1990 | 0 |
|
1991 | 0 | bool shouldCancel; |
1992 | 0 | { |
1993 | 0 | MutexAutoLock lock(mMutex); |
1994 | 0 | shouldCancel = mParentStatus == Canceling; |
1995 | 0 | } |
1996 | 0 |
|
1997 | 0 | if (shouldCancel && !Cancel()) { |
1998 | 0 | return false; |
1999 | 0 | } |
2000 | 0 | } |
2001 | 0 | |
2002 | 0 | return true; |
2003 | 0 | } |
2004 | | |
2005 | | bool |
2006 | | WorkerPrivate::ProxyReleaseMainThreadObjects() |
2007 | 0 | { |
2008 | 0 | AssertIsOnParentThread(); |
2009 | 0 | MOZ_ASSERT(!mMainThreadObjectsForgotten); |
2010 | 0 |
|
2011 | 0 | nsCOMPtr<nsILoadGroup> loadGroupToCancel; |
2012 | 0 | // If we're not overriden, then do nothing here. Let the load group get |
2013 | 0 | // handled in ForgetMainThreadObjects(). |
2014 | 0 | if (mLoadInfo.mInterfaceRequestor) { |
2015 | 0 | mLoadInfo.mLoadGroup.swap(loadGroupToCancel); |
2016 | 0 | } |
2017 | 0 |
|
2018 | 0 | bool result = mLoadInfo.ProxyReleaseMainThreadObjects(this, loadGroupToCancel); |
2019 | 0 |
|
2020 | 0 | mMainThreadObjectsForgotten = true; |
2021 | 0 |
|
2022 | 0 | return result; |
2023 | 0 | } |
2024 | | |
2025 | | void |
2026 | | WorkerPrivate::UpdateContextOptions(const JS::ContextOptions& aContextOptions) |
2027 | 0 | { |
2028 | 0 | AssertIsOnParentThread(); |
2029 | 0 |
|
2030 | 0 | { |
2031 | 0 | MutexAutoLock lock(mMutex); |
2032 | 0 | mJSSettings.contextOptions = aContextOptions; |
2033 | 0 | } |
2034 | 0 |
|
2035 | 0 | RefPtr<UpdateContextOptionsRunnable> runnable = |
2036 | 0 | new UpdateContextOptionsRunnable(this, aContextOptions); |
2037 | 0 | if (!runnable->Dispatch()) { |
2038 | 0 | NS_WARNING("Failed to update worker context options!"); |
2039 | 0 | } |
2040 | 0 | } |
2041 | | |
2042 | | void |
2043 | | WorkerPrivate::UpdateLanguages(const nsTArray<nsString>& aLanguages) |
2044 | 0 | { |
2045 | 0 | AssertIsOnParentThread(); |
2046 | 0 |
|
2047 | 0 | RefPtr<UpdateLanguagesRunnable> runnable = |
2048 | 0 | new UpdateLanguagesRunnable(this, aLanguages); |
2049 | 0 | if (!runnable->Dispatch()) { |
2050 | 0 | NS_WARNING("Failed to update worker languages!"); |
2051 | 0 | } |
2052 | 0 | } |
2053 | | |
2054 | | void |
2055 | | WorkerPrivate::UpdateJSWorkerMemoryParameter(JSGCParamKey aKey, uint32_t aValue) |
2056 | 0 | { |
2057 | 0 | AssertIsOnParentThread(); |
2058 | 0 |
|
2059 | 0 | bool found = false; |
2060 | 0 |
|
2061 | 0 | { |
2062 | 0 | MutexAutoLock lock(mMutex); |
2063 | 0 | found = mJSSettings.ApplyGCSetting(aKey, aValue); |
2064 | 0 | } |
2065 | 0 |
|
2066 | 0 | if (found) { |
2067 | 0 | RefPtr<UpdateJSWorkerMemoryParameterRunnable> runnable = |
2068 | 0 | new UpdateJSWorkerMemoryParameterRunnable(this, aKey, aValue); |
2069 | 0 | if (!runnable->Dispatch()) { |
2070 | 0 | NS_WARNING("Failed to update memory parameter!"); |
2071 | 0 | } |
2072 | 0 | } |
2073 | 0 | } |
2074 | | |
2075 | | #ifdef JS_GC_ZEAL |
2076 | | void |
2077 | | WorkerPrivate::UpdateGCZeal(uint8_t aGCZeal, uint32_t aFrequency) |
2078 | | { |
2079 | | AssertIsOnParentThread(); |
2080 | | |
2081 | | { |
2082 | | MutexAutoLock lock(mMutex); |
2083 | | mJSSettings.gcZeal = aGCZeal; |
2084 | | mJSSettings.gcZealFrequency = aFrequency; |
2085 | | } |
2086 | | |
2087 | | RefPtr<UpdateGCZealRunnable> runnable = |
2088 | | new UpdateGCZealRunnable(this, aGCZeal, aFrequency); |
2089 | | if (!runnable->Dispatch()) { |
2090 | | NS_WARNING("Failed to update worker gczeal!"); |
2091 | | } |
2092 | | } |
2093 | | #endif |
2094 | | |
2095 | | void |
2096 | | WorkerPrivate::GarbageCollect(bool aShrinking) |
2097 | 0 | { |
2098 | 0 | AssertIsOnParentThread(); |
2099 | 0 |
|
2100 | 0 | RefPtr<GarbageCollectRunnable> runnable = |
2101 | 0 | new GarbageCollectRunnable(this, aShrinking, /* collectChildren = */ true); |
2102 | 0 | if (!runnable->Dispatch()) { |
2103 | 0 | NS_WARNING("Failed to GC worker!"); |
2104 | 0 | } |
2105 | 0 | } |
2106 | | |
2107 | | void |
2108 | | WorkerPrivate::CycleCollect(bool aDummy) |
2109 | 0 | { |
2110 | 0 | AssertIsOnParentThread(); |
2111 | 0 |
|
2112 | 0 | RefPtr<CycleCollectRunnable> runnable = |
2113 | 0 | new CycleCollectRunnable(this, /* collectChildren = */ true); |
2114 | 0 | if (!runnable->Dispatch()) { |
2115 | 0 | NS_WARNING("Failed to CC worker!"); |
2116 | 0 | } |
2117 | 0 | } |
2118 | | |
2119 | | void |
2120 | | WorkerPrivate::OfflineStatusChangeEvent(bool aIsOffline) |
2121 | 0 | { |
2122 | 0 | AssertIsOnParentThread(); |
2123 | 0 |
|
2124 | 0 | RefPtr<OfflineStatusChangeRunnable> runnable = |
2125 | 0 | new OfflineStatusChangeRunnable(this, aIsOffline); |
2126 | 0 | if (!runnable->Dispatch()) { |
2127 | 0 | NS_WARNING("Failed to dispatch offline status change event!"); |
2128 | 0 | } |
2129 | 0 | } |
2130 | | |
2131 | | void |
2132 | | WorkerPrivate::OfflineStatusChangeEventInternal(bool aIsOffline) |
2133 | 0 | { |
2134 | 0 | AssertIsOnWorkerThread(); |
2135 | 0 |
|
2136 | 0 | // The worker is already in this state. No need to dispatch an event. |
2137 | 0 | if (mOnLine == !aIsOffline) { |
2138 | 0 | return; |
2139 | 0 | } |
2140 | 0 | |
2141 | 0 | for (uint32_t index = 0; index < mChildWorkers.Length(); ++index) { |
2142 | 0 | mChildWorkers[index]->OfflineStatusChangeEvent(aIsOffline); |
2143 | 0 | } |
2144 | 0 |
|
2145 | 0 | mOnLine = !aIsOffline; |
2146 | 0 | WorkerGlobalScope* globalScope = GlobalScope(); |
2147 | 0 | RefPtr<WorkerNavigator> nav = globalScope->GetExistingNavigator(); |
2148 | 0 | if (nav) { |
2149 | 0 | nav->SetOnLine(mOnLine); |
2150 | 0 | } |
2151 | 0 |
|
2152 | 0 | nsString eventType; |
2153 | 0 | if (aIsOffline) { |
2154 | 0 | eventType.AssignLiteral("offline"); |
2155 | 0 | } else { |
2156 | 0 | eventType.AssignLiteral("online"); |
2157 | 0 | } |
2158 | 0 |
|
2159 | 0 | RefPtr<Event> event = NS_NewDOMEvent(globalScope, nullptr, nullptr); |
2160 | 0 |
|
2161 | 0 | event->InitEvent(eventType, false, false); |
2162 | 0 | event->SetTrusted(true); |
2163 | 0 |
|
2164 | 0 | globalScope->DispatchEvent(*event); |
2165 | 0 | } |
2166 | | |
2167 | | void |
2168 | | WorkerPrivate::MemoryPressure(bool aDummy) |
2169 | 0 | { |
2170 | 0 | AssertIsOnParentThread(); |
2171 | 0 |
|
2172 | 0 | RefPtr<MemoryPressureRunnable> runnable = new MemoryPressureRunnable(this); |
2173 | 0 | Unused << NS_WARN_IF(!runnable->Dispatch()); |
2174 | 0 | } |
2175 | | |
2176 | | bool |
2177 | | WorkerPrivate::RegisterSharedWorker(SharedWorker* aSharedWorker, |
2178 | | MessagePort* aPort) |
2179 | 0 | { |
2180 | 0 | AssertIsOnMainThread(); |
2181 | 0 | MOZ_ASSERT(aSharedWorker); |
2182 | 0 | MOZ_ASSERT(IsSharedWorker()); |
2183 | 0 | MOZ_ASSERT(!mSharedWorkers.Contains(aSharedWorker)); |
2184 | 0 |
|
2185 | 0 | if (IsSharedWorker()) { |
2186 | 0 | RefPtr<MessagePortRunnable> runnable = new MessagePortRunnable(this, aPort); |
2187 | 0 | if (!runnable->Dispatch()) { |
2188 | 0 | return false; |
2189 | 0 | } |
2190 | 0 | } |
2191 | 0 | |
2192 | 0 | mSharedWorkers.AppendElement(aSharedWorker); |
2193 | 0 |
|
2194 | 0 | // If there were other SharedWorker objects attached to this worker then they |
2195 | 0 | // may all have been frozen and this worker would need to be thawed. |
2196 | 0 | if (mSharedWorkers.Length() > 1 && IsFrozen() && !Thaw(nullptr)) { |
2197 | 0 | return false; |
2198 | 0 | } |
2199 | 0 | |
2200 | 0 | return true; |
2201 | 0 | } |
2202 | | |
2203 | | void |
2204 | | WorkerPrivate::BroadcastErrorToSharedWorkers( |
2205 | | JSContext* aCx, |
2206 | | const WorkerErrorReport* aReport, |
2207 | | bool aIsErrorEvent) |
2208 | 0 | { |
2209 | 0 | AssertIsOnMainThread(); |
2210 | 0 |
|
2211 | 0 | if (aIsErrorEvent && JSREPORT_IS_WARNING(aReport->mFlags)) { |
2212 | 0 | // Don't fire any events anywhere. Just log to console. |
2213 | 0 | // XXXbz should we log to all the consoles of all the relevant windows? |
2214 | 0 | MOZ_ASSERT(aReport); |
2215 | 0 | WorkerErrorReport::LogErrorToConsole(*aReport, 0); |
2216 | 0 | return; |
2217 | 0 | } |
2218 | 0 |
|
2219 | 0 | AutoTArray<RefPtr<SharedWorker>, 10> sharedWorkers; |
2220 | 0 | GetAllSharedWorkers(sharedWorkers); |
2221 | 0 |
|
2222 | 0 | if (sharedWorkers.IsEmpty()) { |
2223 | 0 | return; |
2224 | 0 | } |
2225 | 0 | |
2226 | 0 | AutoTArray<WindowAction, 10> windowActions; |
2227 | 0 |
|
2228 | 0 | // First fire the error event at all SharedWorker objects. This may include |
2229 | 0 | // multiple objects in a single window as well as objects in different |
2230 | 0 | // windows. |
2231 | 0 | for (size_t index = 0; index < sharedWorkers.Length(); index++) { |
2232 | 0 | RefPtr<SharedWorker>& sharedWorker = sharedWorkers[index]; |
2233 | 0 |
|
2234 | 0 | // May be null. |
2235 | 0 | nsPIDOMWindowInner* window = sharedWorker->GetOwner(); |
2236 | 0 |
|
2237 | 0 | RefPtr<Event> event; |
2238 | 0 |
|
2239 | 0 | if (aIsErrorEvent) { |
2240 | 0 | RootedDictionary<ErrorEventInit> errorInit(aCx); |
2241 | 0 | errorInit.mBubbles = false; |
2242 | 0 | errorInit.mCancelable = true; |
2243 | 0 | errorInit.mMessage = aReport->mMessage; |
2244 | 0 | errorInit.mFilename = aReport->mFilename; |
2245 | 0 | errorInit.mLineno = aReport->mLineNumber; |
2246 | 0 | errorInit.mColno = aReport->mColumnNumber; |
2247 | 0 |
|
2248 | 0 | event = ErrorEvent::Constructor(sharedWorker, NS_LITERAL_STRING("error"), |
2249 | 0 | errorInit); |
2250 | 0 | } else { |
2251 | 0 | event = Event::Constructor(sharedWorker, NS_LITERAL_STRING("error"), |
2252 | 0 | EventInit()); |
2253 | 0 | } |
2254 | 0 |
|
2255 | 0 | if (!event) { |
2256 | 0 | ThrowAndReport(window, NS_ERROR_UNEXPECTED); |
2257 | 0 | continue; |
2258 | 0 | } |
2259 | 0 | |
2260 | 0 | event->SetTrusted(true); |
2261 | 0 |
|
2262 | 0 | ErrorResult res; |
2263 | 0 | bool defaultActionEnabled = |
2264 | 0 | sharedWorker->DispatchEvent(*event, CallerType::System, res); |
2265 | 0 | if (res.Failed()) { |
2266 | 0 | ThrowAndReport(window, res.StealNSResult()); |
2267 | 0 | continue; |
2268 | 0 | } |
2269 | 0 | |
2270 | 0 | if (!aIsErrorEvent) { |
2271 | 0 | continue; |
2272 | 0 | } |
2273 | 0 | |
2274 | 0 | if (defaultActionEnabled) { |
2275 | 0 | // Add the owning window to our list so that we will fire an error event |
2276 | 0 | // at it later. |
2277 | 0 | if (!windowActions.Contains(window)) { |
2278 | 0 | windowActions.AppendElement(WindowAction(window)); |
2279 | 0 | } |
2280 | 0 | } else { |
2281 | 0 | size_t actionsIndex = windowActions.LastIndexOf(WindowAction(window)); |
2282 | 0 | if (actionsIndex != windowActions.NoIndex) { |
2283 | 0 | // Any listener that calls preventDefault() will prevent the window from |
2284 | 0 | // receiving the error event. |
2285 | 0 | windowActions[actionsIndex].mDefaultAction = false; |
2286 | 0 | } |
2287 | 0 | } |
2288 | 0 | } |
2289 | 0 |
|
2290 | 0 | // If there are no windows to consider further then we're done. |
2291 | 0 | if (windowActions.IsEmpty()) { |
2292 | 0 | return; |
2293 | 0 | } |
2294 | 0 | |
2295 | 0 | bool shouldLogErrorToConsole = true; |
2296 | 0 |
|
2297 | 0 | // Now fire error events at all the windows remaining. |
2298 | 0 | for (uint32_t index = 0; index < windowActions.Length(); index++) { |
2299 | 0 | WindowAction& windowAction = windowActions[index]; |
2300 | 0 |
|
2301 | 0 | // If there is no window or the script already called preventDefault then |
2302 | 0 | // skip this window. |
2303 | 0 | if (!windowAction.mWindow || !windowAction.mDefaultAction) { |
2304 | 0 | continue; |
2305 | 0 | } |
2306 | 0 | |
2307 | 0 | nsCOMPtr<nsIScriptGlobalObject> sgo = |
2308 | 0 | do_QueryInterface(windowAction.mWindow); |
2309 | 0 | MOZ_ASSERT(sgo); |
2310 | 0 |
|
2311 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
2312 | 0 | RootedDictionary<ErrorEventInit> init(aCx); |
2313 | 0 | init.mLineno = aReport->mLineNumber; |
2314 | 0 | init.mFilename = aReport->mFilename; |
2315 | 0 | init.mMessage = aReport->mMessage; |
2316 | 0 | init.mCancelable = true; |
2317 | 0 | init.mBubbles = true; |
2318 | 0 |
|
2319 | 0 | nsEventStatus status = nsEventStatus_eIgnore; |
2320 | 0 | if (!sgo->HandleScriptError(init, &status)) { |
2321 | 0 | ThrowAndReport(windowAction.mWindow, NS_ERROR_UNEXPECTED); |
2322 | 0 | continue; |
2323 | 0 | } |
2324 | 0 | |
2325 | 0 | if (status == nsEventStatus_eConsumeNoDefault) { |
2326 | 0 | shouldLogErrorToConsole = false; |
2327 | 0 | } |
2328 | 0 | } |
2329 | 0 |
|
2330 | 0 | // Finally log a warning in the console if no window tried to prevent it. |
2331 | 0 | if (shouldLogErrorToConsole) { |
2332 | 0 | MOZ_ASSERT(aReport); |
2333 | 0 | WorkerErrorReport::LogErrorToConsole(*aReport, 0); |
2334 | 0 | } |
2335 | 0 | } |
2336 | | |
2337 | | void |
2338 | | WorkerPrivate::GetAllSharedWorkers(nsTArray<RefPtr<SharedWorker>>& aSharedWorkers) |
2339 | 0 | { |
2340 | 0 | AssertIsOnMainThread(); |
2341 | 0 | MOZ_ASSERT(IsSharedWorker() || IsServiceWorker()); |
2342 | 0 |
|
2343 | 0 | if (!aSharedWorkers.IsEmpty()) { |
2344 | 0 | aSharedWorkers.Clear(); |
2345 | 0 | } |
2346 | 0 |
|
2347 | 0 | for (uint32_t i = 0; i < mSharedWorkers.Length(); ++i) { |
2348 | 0 | aSharedWorkers.AppendElement(mSharedWorkers[i]); |
2349 | 0 | } |
2350 | 0 | } |
2351 | | |
2352 | | void |
2353 | | WorkerPrivate::CloseSharedWorkersForWindow(nsPIDOMWindowInner* aWindow) |
2354 | 0 | { |
2355 | 0 | AssertIsOnMainThread(); |
2356 | 0 | MOZ_ASSERT(IsSharedWorker() || IsServiceWorker()); |
2357 | 0 | MOZ_ASSERT(aWindow); |
2358 | 0 |
|
2359 | 0 | bool someRemoved = false; |
2360 | 0 |
|
2361 | 0 | for (uint32_t i = 0; i < mSharedWorkers.Length();) { |
2362 | 0 | if (mSharedWorkers[i]->GetOwner() == aWindow) { |
2363 | 0 | mSharedWorkers[i]->Close(); |
2364 | 0 | mSharedWorkers.RemoveElementAt(i); |
2365 | 0 | someRemoved = true; |
2366 | 0 | } else { |
2367 | 0 | MOZ_ASSERT(!SameCOMIdentity(mSharedWorkers[i]->GetOwner(), aWindow)); |
2368 | 0 | ++i; |
2369 | 0 | } |
2370 | 0 | } |
2371 | 0 |
|
2372 | 0 | if (!someRemoved) { |
2373 | 0 | return; |
2374 | 0 | } |
2375 | 0 | |
2376 | 0 | // If there are still SharedWorker objects attached to this worker then they |
2377 | 0 | // may all be frozen and this worker would need to be frozen. Otherwise, |
2378 | 0 | // if that was the last SharedWorker then it's time to cancel this worker. |
2379 | 0 | |
2380 | 0 | if (!mSharedWorkers.IsEmpty()) { |
2381 | 0 | Freeze(nullptr); |
2382 | 0 | } else { |
2383 | 0 | Cancel(); |
2384 | 0 | } |
2385 | 0 | } |
2386 | | |
2387 | | void |
2388 | | WorkerPrivate::CloseAllSharedWorkers() |
2389 | 0 | { |
2390 | 0 | AssertIsOnMainThread(); |
2391 | 0 | MOZ_ASSERT(IsSharedWorker() || IsServiceWorker()); |
2392 | 0 |
|
2393 | 0 | for (uint32_t i = 0; i < mSharedWorkers.Length(); ++i) { |
2394 | 0 | mSharedWorkers[i]->Close(); |
2395 | 0 | } |
2396 | 0 |
|
2397 | 0 | mSharedWorkers.Clear(); |
2398 | 0 |
|
2399 | 0 | Cancel(); |
2400 | 0 | } |
2401 | | |
2402 | | void |
2403 | | WorkerPrivate::WorkerScriptLoaded() |
2404 | 0 | { |
2405 | 0 | AssertIsOnMainThread(); |
2406 | 0 |
|
2407 | 0 | if (IsSharedWorker() || IsServiceWorker()) { |
2408 | 0 | // No longer need to hold references to the window or document we came from. |
2409 | 0 | mLoadInfo.mWindow = nullptr; |
2410 | 0 | mLoadInfo.mScriptContext = nullptr; |
2411 | 0 | } |
2412 | 0 | } |
2413 | | |
2414 | | void |
2415 | | WorkerPrivate::SetBaseURI(nsIURI* aBaseURI) |
2416 | 0 | { |
2417 | 0 | AssertIsOnMainThread(); |
2418 | 0 |
|
2419 | 0 | if (!mLoadInfo.mBaseURI) { |
2420 | 0 | NS_ASSERTION(GetParent(), "Shouldn't happen without a parent!"); |
2421 | 0 | mLoadInfo.mResolvedScriptURI = aBaseURI; |
2422 | 0 | } |
2423 | 0 |
|
2424 | 0 | mLoadInfo.mBaseURI = aBaseURI; |
2425 | 0 |
|
2426 | 0 | if (NS_FAILED(aBaseURI->GetSpec(mLocationInfo.mHref))) { |
2427 | 0 | mLocationInfo.mHref.Truncate(); |
2428 | 0 | } |
2429 | 0 |
|
2430 | 0 | mLocationInfo.mHostname.Truncate(); |
2431 | 0 | nsContentUtils::GetHostOrIPv6WithBrackets(aBaseURI, mLocationInfo.mHostname); |
2432 | 0 |
|
2433 | 0 | nsCOMPtr<nsIURL> url(do_QueryInterface(aBaseURI)); |
2434 | 0 | if (!url || NS_FAILED(url->GetFilePath(mLocationInfo.mPathname))) { |
2435 | 0 | mLocationInfo.mPathname.Truncate(); |
2436 | 0 | } |
2437 | 0 |
|
2438 | 0 | nsCString temp; |
2439 | 0 |
|
2440 | 0 | if (url && NS_SUCCEEDED(url->GetQuery(temp)) && !temp.IsEmpty()) { |
2441 | 0 | mLocationInfo.mSearch.Assign('?'); |
2442 | 0 | mLocationInfo.mSearch.Append(temp); |
2443 | 0 | } |
2444 | 0 |
|
2445 | 0 | if (NS_SUCCEEDED(aBaseURI->GetRef(temp)) && !temp.IsEmpty()) { |
2446 | 0 | if (mLocationInfo.mHash.IsEmpty()) { |
2447 | 0 | mLocationInfo.mHash.Assign('#'); |
2448 | 0 | mLocationInfo.mHash.Append(temp); |
2449 | 0 | } |
2450 | 0 | } |
2451 | 0 |
|
2452 | 0 | if (NS_SUCCEEDED(aBaseURI->GetScheme(mLocationInfo.mProtocol))) { |
2453 | 0 | mLocationInfo.mProtocol.Append(':'); |
2454 | 0 | } |
2455 | 0 | else { |
2456 | 0 | mLocationInfo.mProtocol.Truncate(); |
2457 | 0 | } |
2458 | 0 |
|
2459 | 0 | int32_t port; |
2460 | 0 | if (NS_SUCCEEDED(aBaseURI->GetPort(&port)) && port != -1) { |
2461 | 0 | mLocationInfo.mPort.AppendInt(port); |
2462 | 0 |
|
2463 | 0 | nsAutoCString host(mLocationInfo.mHostname); |
2464 | 0 | host.Append(':'); |
2465 | 0 | host.Append(mLocationInfo.mPort); |
2466 | 0 |
|
2467 | 0 | mLocationInfo.mHost.Assign(host); |
2468 | 0 | } |
2469 | 0 | else { |
2470 | 0 | mLocationInfo.mHost.Assign(mLocationInfo.mHostname); |
2471 | 0 | } |
2472 | 0 |
|
2473 | 0 | nsContentUtils::GetUTFOrigin(aBaseURI, mLocationInfo.mOrigin); |
2474 | 0 | } |
2475 | | |
2476 | | nsresult |
2477 | | WorkerPrivate::SetPrincipalOnMainThread(nsIPrincipal* aPrincipal, |
2478 | | nsILoadGroup* aLoadGroup) |
2479 | 0 | { |
2480 | 0 | return mLoadInfo.SetPrincipalOnMainThread(aPrincipal, aLoadGroup); |
2481 | 0 | } |
2482 | | |
2483 | | nsresult |
2484 | | WorkerPrivate::SetPrincipalFromChannel(nsIChannel* aChannel) |
2485 | 0 | { |
2486 | 0 | return mLoadInfo.SetPrincipalFromChannel(aChannel); |
2487 | 0 | } |
2488 | | |
2489 | | bool |
2490 | | WorkerPrivate::FinalChannelPrincipalIsValid(nsIChannel* aChannel) |
2491 | 0 | { |
2492 | 0 | return mLoadInfo.FinalChannelPrincipalIsValid(aChannel); |
2493 | 0 | } |
2494 | | |
2495 | | #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED |
2496 | | bool |
2497 | | WorkerPrivate::PrincipalURIMatchesScriptURL() |
2498 | 0 | { |
2499 | 0 | return mLoadInfo.PrincipalURIMatchesScriptURL(); |
2500 | 0 | } |
2501 | | #endif |
2502 | | |
2503 | | void |
2504 | | WorkerPrivate::UpdateOverridenLoadGroup(nsILoadGroup* aBaseLoadGroup) |
2505 | 0 | { |
2506 | 0 | AssertIsOnMainThread(); |
2507 | 0 |
|
2508 | 0 | // The load group should have been overriden at init time. |
2509 | 0 | mLoadInfo.mInterfaceRequestor->MaybeAddTabChild(aBaseLoadGroup); |
2510 | 0 | } |
2511 | | |
2512 | | void |
2513 | | WorkerPrivate::FlushReportsToSharedWorkers(nsIConsoleReportCollector* aReporter) |
2514 | 0 | { |
2515 | 0 | AssertIsOnMainThread(); |
2516 | 0 |
|
2517 | 0 | AutoTArray<RefPtr<SharedWorker>, 10> sharedWorkers; |
2518 | 0 | AutoTArray<WindowAction, 10> windowActions; |
2519 | 0 | GetAllSharedWorkers(sharedWorkers); |
2520 | 0 |
|
2521 | 0 | // First find out all the shared workers' window. |
2522 | 0 | for (size_t index = 0; index < sharedWorkers.Length(); index++) { |
2523 | 0 | RefPtr<SharedWorker>& sharedWorker = sharedWorkers[index]; |
2524 | 0 |
|
2525 | 0 | // May be null. |
2526 | 0 | nsPIDOMWindowInner* window = sharedWorker->GetOwner(); |
2527 | 0 |
|
2528 | 0 | // Add the owning window to our list so that we will flush the reports later. |
2529 | 0 | if (window && !windowActions.Contains(window)) { |
2530 | 0 | windowActions.AppendElement(WindowAction(window)); |
2531 | 0 | } |
2532 | 0 | } |
2533 | 0 |
|
2534 | 0 | bool reportErrorToBrowserConsole = true; |
2535 | 0 |
|
2536 | 0 | // Flush the reports. |
2537 | 0 | for (uint32_t index = 0; index < windowActions.Length(); index++) { |
2538 | 0 | WindowAction& windowAction = windowActions[index]; |
2539 | 0 |
|
2540 | 0 | aReporter->FlushReportsToConsole( |
2541 | 0 | windowAction.mWindow->WindowID(), |
2542 | 0 | nsIConsoleReportCollector::ReportAction::Save); |
2543 | 0 | reportErrorToBrowserConsole = false; |
2544 | 0 | } |
2545 | 0 |
|
2546 | 0 | // Finally report to browser console if there is no any window or shared |
2547 | 0 | // worker. |
2548 | 0 | if (reportErrorToBrowserConsole) { |
2549 | 0 | aReporter->FlushReportsToConsole(0); |
2550 | 0 | return; |
2551 | 0 | } |
2552 | 0 | |
2553 | 0 | aReporter->ClearConsoleReports(); |
2554 | 0 | } |
2555 | | |
2556 | | #ifdef DEBUG |
2557 | | |
2558 | | void |
2559 | | WorkerPrivate::AssertIsOnParentThread() const |
2560 | | { |
2561 | | if (GetParent()) { |
2562 | | GetParent()->AssertIsOnWorkerThread(); |
2563 | | } else { |
2564 | | AssertIsOnMainThread(); |
2565 | | } |
2566 | | } |
2567 | | |
2568 | | void |
2569 | | WorkerPrivate::AssertInnerWindowIsCorrect() const |
2570 | | { |
2571 | | AssertIsOnParentThread(); |
2572 | | |
2573 | | // Only care about top level workers from windows. |
2574 | | if (mParent || !mLoadInfo.mWindow) { |
2575 | | return; |
2576 | | } |
2577 | | |
2578 | | AssertIsOnMainThread(); |
2579 | | |
2580 | | nsPIDOMWindowOuter* outer = mLoadInfo.mWindow->GetOuterWindow(); |
2581 | | NS_ASSERTION(outer && outer->GetCurrentInnerWindow() == mLoadInfo.mWindow, |
2582 | | "Inner window no longer correct!"); |
2583 | | } |
2584 | | |
2585 | | #endif |
2586 | | |
2587 | | #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED |
2588 | | bool |
2589 | | WorkerPrivate::PrincipalIsValid() const |
2590 | 0 | { |
2591 | 0 | return mLoadInfo.PrincipalIsValid(); |
2592 | 0 | } |
2593 | | #endif |
2594 | | |
2595 | | WorkerPrivate::WorkerPrivate(WorkerPrivate* aParent, |
2596 | | const nsAString& aScriptURL, |
2597 | | bool aIsChromeWorker, WorkerType aWorkerType, |
2598 | | const nsAString& aWorkerName, |
2599 | | const nsACString& aServiceWorkerScope, |
2600 | | WorkerLoadInfo& aLoadInfo) |
2601 | | : mMutex("WorkerPrivate Mutex") |
2602 | | , mCondVar(mMutex, "WorkerPrivate CondVar") |
2603 | | , mParent(aParent) |
2604 | | , mScriptURL(aScriptURL) |
2605 | | , mWorkerName(aWorkerName) |
2606 | | , mWorkerType(aWorkerType) |
2607 | | , mDebugger(nullptr) |
2608 | | , mJSContext(nullptr) |
2609 | | , mPRThread(nullptr) |
2610 | | , mMainThreadEventTarget(GetMainThreadEventTarget()) |
2611 | | , mWorkerControlEventTarget(new WorkerEventTarget(this, |
2612 | | WorkerEventTarget::Behavior::ControlOnly)) |
2613 | | , mWorkerHybridEventTarget(new WorkerEventTarget(this, |
2614 | | WorkerEventTarget::Behavior::Hybrid)) |
2615 | | , mParentStatus(Pending) |
2616 | | , mStatus(Pending) |
2617 | | , mBusyCount(0) |
2618 | | , mLoadingWorkerScript(false) |
2619 | | , mCreationTimeStamp(TimeStamp::Now()) |
2620 | | , mCreationTimeHighRes((double)PR_Now() / PR_USEC_PER_MSEC) |
2621 | | , mNumHoldersPreventingShutdownStart(0) |
2622 | | , mDebuggerEventLoopLevel(0) |
2623 | | , mErrorHandlerRecursionCount(0) |
2624 | | , mNextTimeoutId(1) |
2625 | | , mParentWindowPausedDepth(0) |
2626 | | , mFrozen(false) |
2627 | | , mTimerRunning(false) |
2628 | | , mRunningExpiredTimeouts(false) |
2629 | | , mPendingEventQueueClearing(false) |
2630 | | , mCancelAllPendingRunnables(false) |
2631 | | , mPeriodicGCTimerRunning(false) |
2632 | | , mIdleGCTimerRunning(false) |
2633 | | , mWorkerScriptExecutedSuccessfully(false) |
2634 | | , mFetchHandlerWasAdded(false) |
2635 | | , mOnLine(false) |
2636 | | , mMainThreadObjectsForgotten(false) |
2637 | | , mIsChromeWorker(aIsChromeWorker) |
2638 | | , mParentFrozen(false) |
2639 | | , mIsSecureContext(false) |
2640 | | , mDebuggerRegistered(false) |
2641 | | , mIsInAutomation(false) |
2642 | | , mPerformanceCounter(nullptr) |
2643 | 0 | { |
2644 | 0 | MOZ_ASSERT_IF(!IsDedicatedWorker(), NS_IsMainThread()); |
2645 | 0 | mLoadInfo.StealFrom(aLoadInfo); |
2646 | 0 |
|
2647 | 0 | if (aParent) { |
2648 | 0 | aParent->AssertIsOnWorkerThread(); |
2649 | 0 |
|
2650 | 0 | // Note that this copies our parent's secure context state into mJSSettings. |
2651 | 0 | aParent->CopyJSSettings(mJSSettings); |
2652 | 0 |
|
2653 | 0 | // And manually set our mIsSecureContext, though it's not really relevant to |
2654 | 0 | // dedicated workers... |
2655 | 0 | mIsSecureContext = aParent->IsSecureContext(); |
2656 | 0 | MOZ_ASSERT_IF(mIsChromeWorker, mIsSecureContext); |
2657 | 0 |
|
2658 | 0 | mIsInAutomation = aParent->IsInAutomation(); |
2659 | 0 |
|
2660 | 0 | MOZ_ASSERT(IsDedicatedWorker()); |
2661 | 0 |
|
2662 | 0 | if (aParent->mParentFrozen) { |
2663 | 0 | Freeze(nullptr); |
2664 | 0 | } |
2665 | 0 |
|
2666 | 0 | mOnLine = aParent->OnLine(); |
2667 | 0 | } |
2668 | 0 | else { |
2669 | 0 | AssertIsOnMainThread(); |
2670 | 0 |
|
2671 | 0 | RuntimeService::GetDefaultJSSettings(mJSSettings); |
2672 | 0 |
|
2673 | 0 | // Our secure context state depends on the kind of worker we have. |
2674 | 0 | if (UsesSystemPrincipal() || IsServiceWorker()) { |
2675 | 0 | mIsSecureContext = true; |
2676 | 0 | } else if (mLoadInfo.mWindow) { |
2677 | 0 | // Shared and dedicated workers both inherit the loading window's secure |
2678 | 0 | // context state. Shared workers then prevent windows with a different |
2679 | 0 | // secure context state from attaching to them. |
2680 | 0 | mIsSecureContext = mLoadInfo.mWindow->IsSecureContext(); |
2681 | 0 | } else { |
2682 | 0 | MOZ_ASSERT_UNREACHABLE("non-chrome worker that is not a service worker " |
2683 | 0 | "that has no parent and no associated window"); |
2684 | 0 | } |
2685 | 0 |
|
2686 | 0 | if (mIsSecureContext) { |
2687 | 0 | mJSSettings.chrome.realmOptions |
2688 | 0 | .creationOptions().setSecureContext(true); |
2689 | 0 | mJSSettings.chrome.realmOptions |
2690 | 0 | .creationOptions().setClampAndJitterTime(false); |
2691 | 0 | mJSSettings.content.realmOptions |
2692 | 0 | .creationOptions().setSecureContext(true); |
2693 | 0 | mJSSettings.content.realmOptions |
2694 | 0 | .creationOptions().setClampAndJitterTime(false); |
2695 | 0 | } |
2696 | 0 |
|
2697 | 0 | mIsInAutomation = xpc::IsInAutomation(); |
2698 | 0 |
|
2699 | 0 | // Our parent can get suspended after it initiates the async creation |
2700 | 0 | // of a new worker thread. In this case suspend the new worker as well. |
2701 | 0 | if (mLoadInfo.mWindow && mLoadInfo.mWindow->IsSuspended()) { |
2702 | 0 | ParentWindowPaused(); |
2703 | 0 | } |
2704 | 0 |
|
2705 | 0 | if (mLoadInfo.mWindow && mLoadInfo.mWindow->IsFrozen()) { |
2706 | 0 | Freeze(mLoadInfo.mWindow); |
2707 | 0 | } |
2708 | 0 |
|
2709 | 0 | mOnLine = !NS_IsOffline(); |
2710 | 0 | } |
2711 | 0 |
|
2712 | 0 | nsCOMPtr<nsISerialEventTarget> target; |
2713 | 0 |
|
2714 | 0 | // A child worker just inherits the parent workers ThrottledEventQueue |
2715 | 0 | // and main thread target for now. This is mainly due to the restriction |
2716 | 0 | // that ThrottledEventQueue can only be created on the main thread at the |
2717 | 0 | // moment. |
2718 | 0 | if (aParent) { |
2719 | 0 | mMainThreadThrottledEventQueue = aParent->mMainThreadThrottledEventQueue; |
2720 | 0 | mMainThreadEventTarget = aParent->mMainThreadEventTarget; |
2721 | 0 | return; |
2722 | 0 | } |
2723 | 0 | |
2724 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
2725 | 0 | target = GetWindow() ? GetWindow()->EventTargetFor(TaskCategory::Worker) : nullptr; |
2726 | 0 |
|
2727 | 0 | if (!target) { |
2728 | 0 | target = GetMainThreadSerialEventTarget(); |
2729 | 0 | MOZ_DIAGNOSTIC_ASSERT(target); |
2730 | 0 | } |
2731 | 0 |
|
2732 | 0 | // Throttle events to the main thread using a ThrottledEventQueue specific to |
2733 | 0 | // this worker thread. This may return nullptr during shutdown. |
2734 | 0 | mMainThreadThrottledEventQueue = ThrottledEventQueue::Create(target); |
2735 | 0 |
|
2736 | 0 | // If we were able to creat the throttled event queue, then use it for |
2737 | 0 | // dispatching our main thread runnables. Otherwise use our underlying |
2738 | 0 | // base target. |
2739 | 0 | if (mMainThreadThrottledEventQueue) { |
2740 | 0 | mMainThreadEventTarget = mMainThreadThrottledEventQueue; |
2741 | 0 | } else { |
2742 | 0 | mMainThreadEventTarget = target.forget(); |
2743 | 0 | } |
2744 | 0 | } |
2745 | | |
2746 | | WorkerPrivate::~WorkerPrivate() |
2747 | 0 | { |
2748 | 0 | DropJSObjects(this); |
2749 | 0 |
|
2750 | 0 | mWorkerControlEventTarget->ForgetWorkerPrivate(this); |
2751 | 0 |
|
2752 | 0 | // We force the hybrid event target to forget the thread when we |
2753 | 0 | // enter the Killing state, but we do it again here to be safe. |
2754 | 0 | // Its possible that we may be created and destroyed without progressing |
2755 | 0 | // to Killing via some obscure code path. |
2756 | 0 | mWorkerHybridEventTarget->ForgetWorkerPrivate(this); |
2757 | 0 | } |
2758 | | |
2759 | | // static |
2760 | | already_AddRefed<WorkerPrivate> |
2761 | | WorkerPrivate::Constructor(JSContext* aCx, |
2762 | | const nsAString& aScriptURL, |
2763 | | bool aIsChromeWorker, WorkerType aWorkerType, |
2764 | | const nsAString& aWorkerName, |
2765 | | const nsACString& aServiceWorkerScope, |
2766 | | WorkerLoadInfo* aLoadInfo, ErrorResult& aRv) |
2767 | 0 | { |
2768 | 0 | WorkerPrivate* parent = NS_IsMainThread() ? |
2769 | 0 | nullptr : |
2770 | 0 | GetCurrentThreadWorkerPrivate(); |
2771 | 0 |
|
2772 | 0 | // If this is a sub-worker, we need to keep the parent worker alive until this |
2773 | 0 | // one is registered. |
2774 | 0 | RefPtr<StrongWorkerRef> workerRef; |
2775 | 0 | if (parent) { |
2776 | 0 | parent->AssertIsOnWorkerThread(); |
2777 | 0 |
|
2778 | 0 | workerRef = |
2779 | 0 | StrongWorkerRef::Create(parent, "WorkerPrivate::Constructor"); |
2780 | 0 | if (NS_WARN_IF(!workerRef)) { |
2781 | 0 | aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
2782 | 0 | return nullptr; |
2783 | 0 | } |
2784 | 0 | } else { |
2785 | 0 | AssertIsOnMainThread(); |
2786 | 0 | } |
2787 | 0 |
|
2788 | 0 | Maybe<WorkerLoadInfo> stackLoadInfo; |
2789 | 0 | if (!aLoadInfo) { |
2790 | 0 | stackLoadInfo.emplace(); |
2791 | 0 |
|
2792 | 0 | nsresult rv = GetLoadInfo(aCx, nullptr, parent, aScriptURL, |
2793 | 0 | aIsChromeWorker, InheritLoadGroup, |
2794 | 0 | aWorkerType, stackLoadInfo.ptr()); |
2795 | 0 | aRv.MightThrowJSException(); |
2796 | 0 | if (NS_FAILED(rv)) { |
2797 | 0 | workerinternals::ReportLoadError(aRv, rv, aScriptURL); |
2798 | 0 | return nullptr; |
2799 | 0 | } |
2800 | 0 | |
2801 | 0 | aLoadInfo = stackLoadInfo.ptr(); |
2802 | 0 | } |
2803 | 0 |
|
2804 | 0 | // NB: This has to be done before creating the WorkerPrivate, because it will |
2805 | 0 | // attempt to use static variables that are initialized in the RuntimeService |
2806 | 0 | // constructor. |
2807 | 0 | RuntimeService* runtimeService; |
2808 | 0 |
|
2809 | 0 | if (!parent) { |
2810 | 0 | runtimeService = RuntimeService::GetOrCreateService(); |
2811 | 0 | if (!runtimeService) { |
2812 | 0 | aRv.Throw(NS_ERROR_FAILURE); |
2813 | 0 | return nullptr; |
2814 | 0 | } |
2815 | 0 | } |
2816 | 0 | else { |
2817 | 0 | runtimeService = RuntimeService::GetService(); |
2818 | 0 | } |
2819 | 0 |
|
2820 | 0 | MOZ_ASSERT(runtimeService); |
2821 | 0 |
|
2822 | 0 | RefPtr<WorkerPrivate> worker = |
2823 | 0 | new WorkerPrivate(parent, aScriptURL, aIsChromeWorker, |
2824 | 0 | aWorkerType, aWorkerName, aServiceWorkerScope, |
2825 | 0 | *aLoadInfo); |
2826 | 0 |
|
2827 | 0 | // Gecko contexts always have an explicitly-set default locale (set by |
2828 | 0 | // XPJSRuntime::Initialize for the main thread, set by |
2829 | 0 | // WorkerThreadPrimaryRunnable::Run for workers just before running worker |
2830 | 0 | // code), so this is never SpiderMonkey's builtin default locale. |
2831 | 0 | JS::UniqueChars defaultLocale = JS_GetDefaultLocale(aCx); |
2832 | 0 | if (NS_WARN_IF(!defaultLocale)) { |
2833 | 0 | aRv.Throw(NS_ERROR_UNEXPECTED); |
2834 | 0 | return nullptr; |
2835 | 0 | } |
2836 | 0 | |
2837 | 0 | worker->mDefaultLocale = std::move(defaultLocale); |
2838 | 0 |
|
2839 | 0 | if (!runtimeService->RegisterWorker(worker)) { |
2840 | 0 | aRv.Throw(NS_ERROR_UNEXPECTED); |
2841 | 0 | return nullptr; |
2842 | 0 | } |
2843 | 0 | |
2844 | 0 | worker->EnableDebugger(); |
2845 | 0 |
|
2846 | 0 | MOZ_DIAGNOSTIC_ASSERT(worker->PrincipalIsValid()); |
2847 | 0 |
|
2848 | 0 | RefPtr<CompileScriptRunnable> compiler = |
2849 | 0 | new CompileScriptRunnable(worker, aScriptURL); |
2850 | 0 | if (!compiler->Dispatch()) { |
2851 | 0 | aRv.Throw(NS_ERROR_UNEXPECTED); |
2852 | 0 | return nullptr; |
2853 | 0 | } |
2854 | 0 | |
2855 | 0 | worker->mSelfRef = worker; |
2856 | 0 |
|
2857 | 0 | return worker.forget(); |
2858 | 0 | } |
2859 | | |
2860 | | // static |
2861 | | nsresult |
2862 | | WorkerPrivate::GetLoadInfo(JSContext* aCx, nsPIDOMWindowInner* aWindow, |
2863 | | WorkerPrivate* aParent, const nsAString& aScriptURL, |
2864 | | bool aIsChromeWorker, |
2865 | | LoadGroupBehavior aLoadGroupBehavior, |
2866 | | WorkerType aWorkerType, |
2867 | | WorkerLoadInfo* aLoadInfo) |
2868 | 0 | { |
2869 | 0 | using namespace mozilla::dom::workerinternals; |
2870 | 0 |
|
2871 | 0 | MOZ_ASSERT(aCx); |
2872 | 0 | MOZ_ASSERT_IF(NS_IsMainThread(), aCx == nsContentUtils::GetCurrentJSContext()); |
2873 | 0 |
|
2874 | 0 | if (aWindow) { |
2875 | 0 | AssertIsOnMainThread(); |
2876 | 0 | } |
2877 | 0 |
|
2878 | 0 | WorkerLoadInfo loadInfo; |
2879 | 0 | nsresult rv; |
2880 | 0 |
|
2881 | 0 | if (aParent) { |
2882 | 0 | aParent->AssertIsOnWorkerThread(); |
2883 | 0 |
|
2884 | 0 | // If the parent is going away give up now. |
2885 | 0 | WorkerStatus parentStatus; |
2886 | 0 | { |
2887 | 0 | MutexAutoLock lock(aParent->mMutex); |
2888 | 0 | parentStatus = aParent->mStatus; |
2889 | 0 | } |
2890 | 0 |
|
2891 | 0 | if (parentStatus > Running) { |
2892 | 0 | return NS_ERROR_FAILURE; |
2893 | 0 | } |
2894 | 0 | |
2895 | 0 | // Passing a pointer to our stack loadInfo is safe here because this |
2896 | 0 | // method uses a sync runnable to get the channel from the main thread. |
2897 | 0 | rv = ChannelFromScriptURLWorkerThread(aCx, aParent, aScriptURL, |
2898 | 0 | loadInfo); |
2899 | 0 | if (NS_FAILED(rv)) { |
2900 | 0 | MOZ_ALWAYS_TRUE(loadInfo.ProxyReleaseMainThreadObjects(aParent)); |
2901 | 0 | return rv; |
2902 | 0 | } |
2903 | 0 |
|
2904 | 0 | // Now that we've spun the loop there's no guarantee that our parent is |
2905 | 0 | // still alive. We may have received control messages initiating shutdown. |
2906 | 0 | { |
2907 | 0 | MutexAutoLock lock(aParent->mMutex); |
2908 | 0 | parentStatus = aParent->mStatus; |
2909 | 0 | } |
2910 | 0 |
|
2911 | 0 | if (parentStatus > Running) { |
2912 | 0 | MOZ_ALWAYS_TRUE(loadInfo.ProxyReleaseMainThreadObjects(aParent)); |
2913 | 0 | return NS_ERROR_FAILURE; |
2914 | 0 | } |
2915 | 0 |
|
2916 | 0 | loadInfo.mDomain = aParent->Domain(); |
2917 | 0 | loadInfo.mFromWindow = aParent->IsFromWindow(); |
2918 | 0 | loadInfo.mWindowID = aParent->WindowID(); |
2919 | 0 | loadInfo.mStorageAllowed = aParent->IsStorageAllowed(); |
2920 | 0 | loadInfo.mOriginAttributes = aParent->GetOriginAttributes(); |
2921 | 0 | loadInfo.mServiceWorkersTestingInWindow = |
2922 | 0 | aParent->ServiceWorkersTestingInWindow(); |
2923 | 0 | loadInfo.mParentController = aParent->GetController(); |
2924 | 0 | } else { |
2925 | 0 | AssertIsOnMainThread(); |
2926 | 0 |
|
2927 | 0 | // Make sure that the IndexedDatabaseManager is set up |
2928 | 0 | Unused << NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate()); |
2929 | 0 |
|
2930 | 0 | nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); |
2931 | 0 | MOZ_ASSERT(ssm); |
2932 | 0 |
|
2933 | 0 | bool isChrome = nsContentUtils::IsSystemCaller(aCx); |
2934 | 0 |
|
2935 | 0 | // First check to make sure the caller has permission to make a privileged |
2936 | 0 | // worker if they called the ChromeWorker/ChromeSharedWorker constructor. |
2937 | 0 | if (aIsChromeWorker && !isChrome) { |
2938 | 0 | return NS_ERROR_DOM_SECURITY_ERR; |
2939 | 0 | } |
2940 | 0 | |
2941 | 0 | // Chrome callers (whether creating a ChromeWorker or Worker) always get the |
2942 | 0 | // system principal here as they're allowed to load anything. The script |
2943 | 0 | // loader will refuse to run any script that does not also have the system |
2944 | 0 | // principal. |
2945 | 0 | if (isChrome) { |
2946 | 0 | rv = ssm->GetSystemPrincipal(getter_AddRefs(loadInfo.mLoadingPrincipal)); |
2947 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
2948 | 0 |
|
2949 | 0 | loadInfo.mPrincipalIsSystem = true; |
2950 | 0 | } |
2951 | 0 |
|
2952 | 0 | // See if we're being called from a window. |
2953 | 0 | nsCOMPtr<nsPIDOMWindowInner> globalWindow = aWindow; |
2954 | 0 | if (!globalWindow) { |
2955 | 0 | globalWindow = xpc::CurrentWindowOrNull(aCx); |
2956 | 0 | } |
2957 | 0 |
|
2958 | 0 | nsCOMPtr<nsIDocument> document; |
2959 | 0 | Maybe<ClientInfo> clientInfo; |
2960 | 0 |
|
2961 | 0 | if (globalWindow) { |
2962 | 0 | // Only use the current inner window, and only use it if the caller can |
2963 | 0 | // access it. |
2964 | 0 | if (nsPIDOMWindowOuter* outerWindow = globalWindow->GetOuterWindow()) { |
2965 | 0 | loadInfo.mWindow = outerWindow->GetCurrentInnerWindow(); |
2966 | 0 | // TODO: fix this for SharedWorkers with multiple documents (bug 1177935) |
2967 | 0 | loadInfo.mServiceWorkersTestingInWindow = |
2968 | 0 | outerWindow->GetServiceWorkersTestingEnabled(); |
2969 | 0 | } |
2970 | 0 |
|
2971 | 0 | if (!loadInfo.mWindow || |
2972 | 0 | (globalWindow != loadInfo.mWindow && |
2973 | 0 | !nsContentUtils::CanCallerAccess(loadInfo.mWindow))) { |
2974 | 0 | return NS_ERROR_DOM_SECURITY_ERR; |
2975 | 0 | } |
2976 | 0 | |
2977 | 0 | nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(loadInfo.mWindow); |
2978 | 0 | MOZ_ASSERT(sgo); |
2979 | 0 |
|
2980 | 0 | loadInfo.mScriptContext = sgo->GetContext(); |
2981 | 0 | NS_ENSURE_TRUE(loadInfo.mScriptContext, NS_ERROR_FAILURE); |
2982 | 0 |
|
2983 | 0 | // If we're called from a window then we can dig out the principal and URI |
2984 | 0 | // from the document. |
2985 | 0 | document = loadInfo.mWindow->GetExtantDoc(); |
2986 | 0 | NS_ENSURE_TRUE(document, NS_ERROR_FAILURE); |
2987 | 0 |
|
2988 | 0 | loadInfo.mBaseURI = document->GetDocBaseURI(); |
2989 | 0 | loadInfo.mLoadGroup = document->GetDocumentLoadGroup(); |
2990 | 0 | NS_ENSURE_TRUE(loadInfo.mLoadGroup, NS_ERROR_FAILURE); |
2991 | 0 |
|
2992 | 0 | clientInfo = globalWindow->GetClientInfo(); |
2993 | 0 |
|
2994 | 0 | // Use the document's NodePrincipal as loading principal if we're not being |
2995 | 0 | // called from chrome. |
2996 | 0 | if (!loadInfo.mLoadingPrincipal) { |
2997 | 0 | loadInfo.mLoadingPrincipal = document->NodePrincipal(); |
2998 | 0 | NS_ENSURE_TRUE(loadInfo.mLoadingPrincipal, NS_ERROR_FAILURE); |
2999 | 0 |
|
3000 | 0 | // We use the document's base domain to limit the number of workers |
3001 | 0 | // each domain can create. For sandboxed documents, we use the domain |
3002 | 0 | // of their first non-sandboxed document, walking up until we find |
3003 | 0 | // one. If we can't find one, we fall back to using the GUID of the |
3004 | 0 | // null principal as the base domain. |
3005 | 0 | if (document->GetSandboxFlags() & SANDBOXED_ORIGIN) { |
3006 | 0 | nsCOMPtr<nsIDocument> tmpDoc = document; |
3007 | 0 | do { |
3008 | 0 | tmpDoc = tmpDoc->GetParentDocument(); |
3009 | 0 | } while (tmpDoc && tmpDoc->GetSandboxFlags() & SANDBOXED_ORIGIN); |
3010 | 0 |
|
3011 | 0 | if (tmpDoc) { |
3012 | 0 | // There was an unsandboxed ancestor, yay! |
3013 | 0 | nsCOMPtr<nsIPrincipal> tmpPrincipal = tmpDoc->NodePrincipal(); |
3014 | 0 | rv = tmpPrincipal->GetBaseDomain(loadInfo.mDomain); |
3015 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
3016 | 0 | } else { |
3017 | 0 | // No unsandboxed ancestor, use our GUID. |
3018 | 0 | rv = loadInfo.mLoadingPrincipal->GetBaseDomain(loadInfo.mDomain); |
3019 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
3020 | 0 | } |
3021 | 0 | } else { |
3022 | 0 | // Document creating the worker is not sandboxed. |
3023 | 0 | rv = loadInfo.mLoadingPrincipal->GetBaseDomain(loadInfo.mDomain); |
3024 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
3025 | 0 | } |
3026 | 0 | } |
3027 | 0 |
|
3028 | 0 | NS_ENSURE_TRUE(NS_LoadGroupMatchesPrincipal(loadInfo.mLoadGroup, |
3029 | 0 | loadInfo.mLoadingPrincipal), |
3030 | 0 | NS_ERROR_FAILURE); |
3031 | 0 |
|
3032 | 0 | nsCOMPtr<nsIPermissionManager> permMgr = |
3033 | 0 | do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); |
3034 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
3035 | 0 |
|
3036 | 0 | uint32_t perm; |
3037 | 0 | rv = permMgr->TestPermissionFromPrincipal(loadInfo.mLoadingPrincipal, |
3038 | 0 | "systemXHR", &perm); |
3039 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
3040 | 0 |
|
3041 | 0 | loadInfo.mXHRParamsAllowed = perm == nsIPermissionManager::ALLOW_ACTION; |
3042 | 0 |
|
3043 | 0 | loadInfo.mFromWindow = true; |
3044 | 0 | loadInfo.mWindowID = globalWindow->WindowID(); |
3045 | 0 | nsContentUtils::StorageAccess access = |
3046 | 0 | nsContentUtils::StorageAllowedForWindow(globalWindow); |
3047 | 0 | loadInfo.mStorageAllowed = access > nsContentUtils::StorageAccess::eDeny; |
3048 | 0 | loadInfo.mOriginAttributes = nsContentUtils::GetOriginAttributes(document); |
3049 | 0 | loadInfo.mParentController = globalWindow->GetController(); |
3050 | 0 | } else { |
3051 | 0 | // Not a window |
3052 | 0 | MOZ_ASSERT(isChrome); |
3053 | 0 |
|
3054 | 0 | // We're being created outside of a window. Need to figure out the script |
3055 | 0 | // that is creating us in order for us to use relative URIs later on. |
3056 | 0 | JS::AutoFilename fileName; |
3057 | 0 | if (JS::DescribeScriptedCaller(aCx, &fileName)) { |
3058 | 0 | // In most cases, fileName is URI. In a few other cases |
3059 | 0 | // (e.g. xpcshell), fileName is a file path. Ideally, we would |
3060 | 0 | // prefer testing whether fileName parses as an URI and fallback |
3061 | 0 | // to file path in case of error, but Windows file paths have |
3062 | 0 | // the interesting property that they can be parsed as bogus |
3063 | 0 | // URIs (e.g. C:/Windows/Tmp is interpreted as scheme "C", |
3064 | 0 | // hostname "Windows", path "Tmp"), which defeats this algorithm. |
3065 | 0 | // Therefore, we adopt the opposite convention. |
3066 | 0 | nsCOMPtr<nsIFile> scriptFile = |
3067 | 0 | do_CreateInstance("@mozilla.org/file/local;1", &rv); |
3068 | 0 | if (NS_FAILED(rv)) { |
3069 | 0 | return rv; |
3070 | 0 | } |
3071 | 0 | |
3072 | 0 | rv = scriptFile->InitWithPath(NS_ConvertUTF8toUTF16(fileName.get())); |
3073 | 0 | if (NS_SUCCEEDED(rv)) { |
3074 | 0 | rv = NS_NewFileURI(getter_AddRefs(loadInfo.mBaseURI), |
3075 | 0 | scriptFile); |
3076 | 0 | } |
3077 | 0 | if (NS_FAILED(rv)) { |
3078 | 0 | // As expected, fileName is not a path, so proceed with |
3079 | 0 | // a uri. |
3080 | 0 | rv = NS_NewURI(getter_AddRefs(loadInfo.mBaseURI), |
3081 | 0 | fileName.get()); |
3082 | 0 | } |
3083 | 0 | if (NS_FAILED(rv)) { |
3084 | 0 | return rv; |
3085 | 0 | } |
3086 | 0 | } |
3087 | 0 | loadInfo.mXHRParamsAllowed = true; |
3088 | 0 | loadInfo.mFromWindow = false; |
3089 | 0 | loadInfo.mWindowID = UINT64_MAX; |
3090 | 0 | loadInfo.mStorageAllowed = true; |
3091 | 0 | loadInfo.mOriginAttributes = OriginAttributes(); |
3092 | 0 | } |
3093 | 0 |
|
3094 | 0 | MOZ_ASSERT(loadInfo.mLoadingPrincipal); |
3095 | 0 | MOZ_ASSERT(isChrome || !loadInfo.mDomain.IsEmpty()); |
3096 | 0 |
|
3097 | 0 | if (!loadInfo.mLoadGroup || aLoadGroupBehavior == OverrideLoadGroup) { |
3098 | 0 | OverrideLoadInfoLoadGroup(loadInfo, loadInfo.mLoadingPrincipal); |
3099 | 0 | } |
3100 | 0 | MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(loadInfo.mLoadGroup, |
3101 | 0 | loadInfo.mLoadingPrincipal)); |
3102 | 0 |
|
3103 | 0 | // Top level workers' main script use the document charset for the script |
3104 | 0 | // uri encoding. |
3105 | 0 | bool useDefaultEncoding = false; |
3106 | 0 | rv = ChannelFromScriptURLMainThread(loadInfo.mLoadingPrincipal, |
3107 | 0 | loadInfo.mBaseURI, |
3108 | 0 | document, loadInfo.mLoadGroup, |
3109 | 0 | aScriptURL, |
3110 | 0 | clientInfo, |
3111 | 0 | ContentPolicyType(aWorkerType), |
3112 | 0 | useDefaultEncoding, |
3113 | 0 | getter_AddRefs(loadInfo.mChannel)); |
3114 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
3115 | 0 |
|
3116 | 0 | rv = NS_GetFinalChannelURI(loadInfo.mChannel, |
3117 | 0 | getter_AddRefs(loadInfo.mResolvedScriptURI)); |
3118 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
3119 | 0 |
|
3120 | 0 | rv = loadInfo.SetPrincipalFromChannel(loadInfo.mChannel); |
3121 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
3122 | 0 | } |
3123 | 0 |
|
3124 | 0 | MOZ_DIAGNOSTIC_ASSERT(loadInfo.mLoadingPrincipal); |
3125 | 0 | MOZ_DIAGNOSTIC_ASSERT(loadInfo.PrincipalIsValid()); |
3126 | 0 |
|
3127 | 0 | aLoadInfo->StealFrom(loadInfo); |
3128 | 0 | return NS_OK; |
3129 | 0 | } |
3130 | | |
3131 | | // static |
3132 | | void |
3133 | | WorkerPrivate::OverrideLoadInfoLoadGroup(WorkerLoadInfo& aLoadInfo, |
3134 | | nsIPrincipal* aPrincipal) |
3135 | 0 | { |
3136 | 0 | MOZ_ASSERT(!aLoadInfo.mInterfaceRequestor); |
3137 | 0 | MOZ_ASSERT(aLoadInfo.mLoadingPrincipal == aPrincipal); |
3138 | 0 |
|
3139 | 0 | aLoadInfo.mInterfaceRequestor = |
3140 | 0 | new WorkerLoadInfo::InterfaceRequestor(aPrincipal, aLoadInfo.mLoadGroup); |
3141 | 0 | aLoadInfo.mInterfaceRequestor->MaybeAddTabChild(aLoadInfo.mLoadGroup); |
3142 | 0 |
|
3143 | 0 | // NOTE: this defaults the load context to: |
3144 | 0 | // - private browsing = false |
3145 | 0 | // - content = true |
3146 | 0 | // - use remote tabs = false |
3147 | 0 | nsCOMPtr<nsILoadGroup> loadGroup = |
3148 | 0 | do_CreateInstance(NS_LOADGROUP_CONTRACTID); |
3149 | 0 |
|
3150 | 0 | nsresult rv = |
3151 | 0 | loadGroup->SetNotificationCallbacks(aLoadInfo.mInterfaceRequestor); |
3152 | 0 | MOZ_ALWAYS_SUCCEEDS(rv); |
3153 | 0 |
|
3154 | 0 | aLoadInfo.mLoadGroup = loadGroup.forget(); |
3155 | 0 |
|
3156 | 0 | MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(aLoadInfo.mLoadGroup, aPrincipal)); |
3157 | 0 | } |
3158 | | |
3159 | | void |
3160 | | WorkerPrivate::DoRunLoop(JSContext* aCx) |
3161 | 0 | { |
3162 | 0 | AssertIsOnWorkerThread(); |
3163 | 0 | MOZ_ASSERT(mThread); |
3164 | 0 |
|
3165 | 0 | { |
3166 | 0 | MutexAutoLock lock(mMutex); |
3167 | 0 | mJSContext = aCx; |
3168 | 0 |
|
3169 | 0 | MOZ_ASSERT(mStatus == Pending); |
3170 | 0 | mStatus = Running; |
3171 | 0 | } |
3172 | 0 |
|
3173 | 0 | // Now that we've done that, we can go ahead and set up our AutoJSAPI. We |
3174 | 0 | // can't before this point, because it can't find the right JSContext before |
3175 | 0 | // then, since it gets it from our mJSContext. |
3176 | 0 | AutoJSAPI jsapi; |
3177 | 0 | jsapi.Init(); |
3178 | 0 | MOZ_ASSERT(jsapi.cx() == aCx); |
3179 | 0 |
|
3180 | 0 | EnableMemoryReporter(); |
3181 | 0 |
|
3182 | 0 | InitializeGCTimers(); |
3183 | 0 |
|
3184 | 0 | for (;;) { |
3185 | 0 | WorkerStatus currentStatus; |
3186 | 0 | bool debuggerRunnablesPending = false; |
3187 | 0 | bool normalRunnablesPending = false; |
3188 | 0 |
|
3189 | 0 | { |
3190 | 0 | MutexAutoLock lock(mMutex); |
3191 | 0 |
|
3192 | 0 | while (mControlQueue.IsEmpty() && |
3193 | 0 | !(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty()) && |
3194 | 0 | !(normalRunnablesPending = NS_HasPendingEvents(mThread))) { |
3195 | 0 | WaitForWorkerEvents(); |
3196 | 0 | } |
3197 | 0 |
|
3198 | 0 | auto result = ProcessAllControlRunnablesLocked(); |
3199 | 0 | if (result != ProcessAllControlRunnablesResult::Nothing) { |
3200 | 0 | // NB: There's no JS on the stack here, so Abort vs MayContinue is |
3201 | 0 | // irrelevant |
3202 | 0 |
|
3203 | 0 | // The state of the world may have changed, recheck it. |
3204 | 0 | normalRunnablesPending = NS_HasPendingEvents(mThread); |
3205 | 0 | // The debugger queue doesn't get cleared, so we can ignore that. |
3206 | 0 | } |
3207 | 0 |
|
3208 | 0 | currentStatus = mStatus; |
3209 | 0 | } |
3210 | 0 |
|
3211 | 0 | // if all holders are done then we can kill this thread. |
3212 | 0 | if (currentStatus != Running && !HasActiveHolders()) { |
3213 | 0 |
|
3214 | 0 | // Now we are ready to kill the worker thread. |
3215 | 0 | if (currentStatus == Canceling) { |
3216 | 0 | NotifyInternal(Killing); |
3217 | 0 |
|
3218 | | #ifdef DEBUG |
3219 | | { |
3220 | | MutexAutoLock lock(mMutex); |
3221 | | currentStatus = mStatus; |
3222 | | } |
3223 | | MOZ_ASSERT(currentStatus == Killing); |
3224 | | #else |
3225 | | currentStatus = Killing; |
3226 | 0 | #endif |
3227 | 0 | } |
3228 | 0 |
|
3229 | 0 | // If we're supposed to die then we should exit the loop. |
3230 | 0 | if (currentStatus == Killing) { |
3231 | 0 | // The ClientSource should be cleared in NotifyInternal() when we reach |
3232 | 0 | // or pass Canceling. |
3233 | 0 | MOZ_DIAGNOSTIC_ASSERT(!mClientSource); |
3234 | 0 |
|
3235 | 0 | // Flush uncaught rejections immediately, without |
3236 | 0 | // waiting for a next tick. |
3237 | 0 | PromiseDebugging::FlushUncaughtRejections(); |
3238 | 0 |
|
3239 | 0 | ShutdownGCTimers(); |
3240 | 0 |
|
3241 | 0 | DisableMemoryReporter(); |
3242 | 0 |
|
3243 | 0 | { |
3244 | 0 | MutexAutoLock lock(mMutex); |
3245 | 0 |
|
3246 | 0 | mStatus = Dead; |
3247 | 0 | mJSContext = nullptr; |
3248 | 0 | } |
3249 | 0 |
|
3250 | 0 | // After mStatus is set to Dead there can be no more |
3251 | 0 | // WorkerControlRunnables so no need to lock here. |
3252 | 0 | if (!mControlQueue.IsEmpty()) { |
3253 | 0 | WorkerControlRunnable* runnable = nullptr; |
3254 | 0 | while (mControlQueue.Pop(runnable)) { |
3255 | 0 | runnable->Cancel(); |
3256 | 0 | runnable->Release(); |
3257 | 0 | } |
3258 | 0 | } |
3259 | 0 |
|
3260 | 0 | // Unroot the globals |
3261 | 0 | mScope = nullptr; |
3262 | 0 | mDebuggerScope = nullptr; |
3263 | 0 |
|
3264 | 0 | return; |
3265 | 0 | } |
3266 | 0 | } |
3267 | 0 |
|
3268 | 0 | if (debuggerRunnablesPending || normalRunnablesPending) { |
3269 | 0 | // Start the periodic GC timer if it is not already running. |
3270 | 0 | SetGCTimerMode(PeriodicTimer); |
3271 | 0 | } |
3272 | 0 |
|
3273 | 0 | if (debuggerRunnablesPending) { |
3274 | 0 | WorkerRunnable* runnable = nullptr; |
3275 | 0 |
|
3276 | 0 | { |
3277 | 0 | MutexAutoLock lock(mMutex); |
3278 | 0 |
|
3279 | 0 | mDebuggerQueue.Pop(runnable); |
3280 | 0 | debuggerRunnablesPending = !mDebuggerQueue.IsEmpty(); |
3281 | 0 | } |
3282 | 0 |
|
3283 | 0 | MOZ_ASSERT(runnable); |
3284 | 0 | static_cast<nsIRunnable*>(runnable)->Run(); |
3285 | 0 | runnable->Release(); |
3286 | 0 |
|
3287 | 0 | CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get(); |
3288 | 0 | ccjs->PerformDebuggerMicroTaskCheckpoint(); |
3289 | 0 |
|
3290 | 0 | if (debuggerRunnablesPending) { |
3291 | 0 | WorkerDebuggerGlobalScope* globalScope = DebuggerGlobalScope(); |
3292 | 0 | MOZ_ASSERT(globalScope); |
3293 | 0 |
|
3294 | 0 | // Now *might* be a good time to GC. Let the JS engine make the decision. |
3295 | 0 | JSAutoRealm ar(aCx, globalScope->GetGlobalJSObject()); |
3296 | 0 | JS_MaybeGC(aCx); |
3297 | 0 | } |
3298 | 0 | } else if (normalRunnablesPending) { |
3299 | 0 | // Process a single runnable from the main queue. |
3300 | 0 | NS_ProcessNextEvent(mThread, false); |
3301 | 0 |
|
3302 | 0 | normalRunnablesPending = NS_HasPendingEvents(mThread); |
3303 | 0 | if (normalRunnablesPending && GlobalScope()) { |
3304 | 0 | // Now *might* be a good time to GC. Let the JS engine make the decision. |
3305 | 0 | JSAutoRealm ar(aCx, GlobalScope()->GetGlobalJSObject()); |
3306 | 0 | JS_MaybeGC(aCx); |
3307 | 0 | } |
3308 | 0 | } |
3309 | 0 |
|
3310 | 0 | if (!debuggerRunnablesPending && !normalRunnablesPending) { |
3311 | 0 | // Both the debugger event queue and the normal event queue has been |
3312 | 0 | // exhausted, cancel the periodic GC timer and schedule the idle GC timer. |
3313 | 0 | SetGCTimerMode(IdleTimer); |
3314 | 0 | } |
3315 | 0 |
|
3316 | 0 | // If the worker thread is spamming the main thread faster than it can |
3317 | 0 | // process the work, then pause the worker thread until the MT catches |
3318 | 0 | // up. |
3319 | 0 | if (mMainThreadThrottledEventQueue && |
3320 | 0 | mMainThreadThrottledEventQueue->Length() > 5000) { |
3321 | 0 | mMainThreadThrottledEventQueue->AwaitIdle(); |
3322 | 0 | } |
3323 | 0 | } |
3324 | 0 |
|
3325 | 0 | MOZ_CRASH("Shouldn't get here!"); |
3326 | 0 | } |
3327 | | |
3328 | | void |
3329 | | WorkerPrivate::OnProcessNextEvent() |
3330 | 0 | { |
3331 | 0 | AssertIsOnWorkerThread(); |
3332 | 0 |
|
3333 | 0 | uint32_t recursionDepth = CycleCollectedJSContext::Get()->RecursionDepth(); |
3334 | 0 | MOZ_ASSERT(recursionDepth); |
3335 | 0 |
|
3336 | 0 | // Normally we process control runnables in DoRunLoop or RunCurrentSyncLoop. |
3337 | 0 | // However, it's possible that non-worker C++ could spin its own nested event |
3338 | 0 | // loop, and in that case we must ensure that we continue to process control |
3339 | 0 | // runnables here. |
3340 | 0 | if (recursionDepth > 1 && |
3341 | 0 | mSyncLoopStack.Length() < recursionDepth - 1) { |
3342 | 0 | Unused << ProcessAllControlRunnables(); |
3343 | 0 | // There's no running JS, and no state to revalidate, so we can ignore the |
3344 | 0 | // return value. |
3345 | 0 | } |
3346 | 0 | } |
3347 | | |
3348 | | void |
3349 | | WorkerPrivate::AfterProcessNextEvent() |
3350 | 0 | { |
3351 | 0 | AssertIsOnWorkerThread(); |
3352 | 0 | MOZ_ASSERT(CycleCollectedJSContext::Get()->RecursionDepth()); |
3353 | 0 | } |
3354 | | |
3355 | | nsIEventTarget* |
3356 | | WorkerPrivate::MainThreadEventTarget() |
3357 | 0 | { |
3358 | 0 | return mMainThreadEventTarget; |
3359 | 0 | } |
3360 | | |
3361 | | nsresult |
3362 | | WorkerPrivate::DispatchToMainThread(nsIRunnable* aRunnable, uint32_t aFlags) |
3363 | 0 | { |
3364 | 0 | nsCOMPtr<nsIRunnable> r = aRunnable; |
3365 | 0 | return DispatchToMainThread(r.forget(), aFlags); |
3366 | 0 | } |
3367 | | |
3368 | | nsresult |
3369 | | WorkerPrivate::DispatchToMainThread(already_AddRefed<nsIRunnable> aRunnable, |
3370 | | uint32_t aFlags) |
3371 | 0 | { |
3372 | 0 | return mMainThreadEventTarget->Dispatch(std::move(aRunnable), aFlags); |
3373 | 0 | } |
3374 | | |
3375 | | nsISerialEventTarget* |
3376 | | WorkerPrivate::ControlEventTarget() |
3377 | 0 | { |
3378 | 0 | return mWorkerControlEventTarget; |
3379 | 0 | } |
3380 | | |
3381 | | nsISerialEventTarget* |
3382 | | WorkerPrivate::HybridEventTarget() |
3383 | 0 | { |
3384 | 0 | return mWorkerHybridEventTarget; |
3385 | 0 | } |
3386 | | |
3387 | | bool |
3388 | | WorkerPrivate::EnsureClientSource() |
3389 | 0 | { |
3390 | 0 | AssertIsOnWorkerThread(); |
3391 | 0 |
|
3392 | 0 | if (mClientSource) { |
3393 | 0 | return true; |
3394 | 0 | } |
3395 | 0 | |
3396 | 0 | ClientType type; |
3397 | 0 | switch(Type()) { |
3398 | 0 | case WorkerTypeDedicated: |
3399 | 0 | type = ClientType::Worker; |
3400 | 0 | break; |
3401 | 0 | case WorkerTypeShared: |
3402 | 0 | type = ClientType::Sharedworker; |
3403 | 0 | break; |
3404 | 0 | case WorkerTypeService: |
3405 | 0 | type = ClientType::Serviceworker; |
3406 | 0 | break; |
3407 | 0 | default: |
3408 | 0 | MOZ_CRASH("unknown worker type!"); |
3409 | 0 | } |
3410 | 0 |
|
3411 | 0 | mClientSource = ClientManager::CreateSource(type, mWorkerHybridEventTarget, |
3412 | 0 | GetPrincipalInfo()); |
3413 | 0 | MOZ_DIAGNOSTIC_ASSERT(mClientSource); |
3414 | 0 |
|
3415 | 0 | if (mFrozen) { |
3416 | 0 | mClientSource->Freeze(); |
3417 | 0 | } |
3418 | 0 |
|
3419 | 0 | // Shortly after the client is reserved we will try loading the main script |
3420 | 0 | // for the worker. This may get intercepted by the ServiceWorkerManager |
3421 | 0 | // which will then try to create a ClientHandle. Its actually possible for |
3422 | 0 | // the main thread to create this ClientHandle before our IPC message creating |
3423 | 0 | // the ClientSource completes. To avoid this race we synchronously ping our |
3424 | 0 | // parent Client actor here. This ensure the worker ClientSource is created |
3425 | 0 | // in the parent before the main thread might try reaching it with a |
3426 | 0 | // ClientHandle. |
3427 | 0 | // |
3428 | 0 | // An alternative solution would have been to handle the out-of-order operations |
3429 | 0 | // on the parent side. We could have created a small window where we allow |
3430 | 0 | // ClientHandle objects to exist without a ClientSource. We would then time |
3431 | 0 | // out these handles if they stayed orphaned for too long. This approach would |
3432 | 0 | // be much more complex, but also avoid this extra bit of latency when starting |
3433 | 0 | // workers. |
3434 | 0 | // |
3435 | 0 | // Note, we only have to do this for workers that can be controlled by a |
3436 | 0 | // service worker. So avoid the sync overhead here if we are starting a |
3437 | 0 | // service worker or a chrome worker. |
3438 | 0 | if (Type() != WorkerTypeService && !IsChromeWorker()) { |
3439 | 0 | mClientSource->WorkerSyncPing(this); |
3440 | 0 | } |
3441 | 0 |
|
3442 | 0 | return true; |
3443 | 0 | } |
3444 | | |
3445 | | bool |
3446 | | WorkerPrivate::EnsureCSPEventListener() |
3447 | 0 | { |
3448 | 0 | mCSPEventListener = WorkerCSPEventListener::Create(this); |
3449 | 0 | if (NS_WARN_IF(!mCSPEventListener)) { |
3450 | 0 | return false; |
3451 | 0 | } |
3452 | 0 | |
3453 | 0 | if (mLoadInfo.mCSP) { |
3454 | 0 | mLoadInfo.mCSP->SetEventListener(mCSPEventListener); |
3455 | 0 | } |
3456 | 0 |
|
3457 | 0 | return true; |
3458 | 0 | } |
3459 | | |
3460 | | void |
3461 | | WorkerPrivate::EnsurePerformanceStorage() |
3462 | 0 | { |
3463 | 0 | AssertIsOnWorkerThread(); |
3464 | 0 |
|
3465 | 0 | if (!mPerformanceStorage) { |
3466 | 0 | mPerformanceStorage = PerformanceStorageWorker::Create(this); |
3467 | 0 | } |
3468 | 0 | } |
3469 | | |
3470 | | Maybe<ClientInfo> |
3471 | | WorkerPrivate::GetClientInfo() const |
3472 | 0 | { |
3473 | 0 | AssertIsOnWorkerThread(); |
3474 | 0 | Maybe<ClientInfo> clientInfo; |
3475 | 0 | if (!mClientSource) { |
3476 | 0 | MOZ_DIAGNOSTIC_ASSERT(mStatus >= Canceling); |
3477 | 0 | return clientInfo; |
3478 | 0 | } |
3479 | 0 | clientInfo.emplace(mClientSource->Info()); |
3480 | 0 | return clientInfo; |
3481 | 0 | } |
3482 | | |
3483 | | const ClientState |
3484 | | WorkerPrivate::GetClientState() const |
3485 | 0 | { |
3486 | 0 | AssertIsOnWorkerThread(); |
3487 | 0 | MOZ_DIAGNOSTIC_ASSERT(mClientSource); |
3488 | 0 | ClientState state; |
3489 | 0 | mClientSource->SnapshotState(&state); |
3490 | 0 | return state; |
3491 | 0 | } |
3492 | | |
3493 | | const Maybe<ServiceWorkerDescriptor> |
3494 | | WorkerPrivate::GetController() |
3495 | 0 | { |
3496 | 0 | AssertIsOnWorkerThread(); |
3497 | 0 | { |
3498 | 0 | MutexAutoLock lock(mMutex); |
3499 | 0 | if (mStatus >= Canceling) { |
3500 | 0 | return Maybe<ServiceWorkerDescriptor>(); |
3501 | 0 | } |
3502 | 0 | } |
3503 | 0 | MOZ_DIAGNOSTIC_ASSERT(mClientSource); |
3504 | 0 | return mClientSource->GetController(); |
3505 | 0 | } |
3506 | | |
3507 | | void |
3508 | | WorkerPrivate::Control(const ServiceWorkerDescriptor& aServiceWorker) |
3509 | 0 | { |
3510 | 0 | AssertIsOnWorkerThread(); |
3511 | 0 | MOZ_DIAGNOSTIC_ASSERT(!IsChromeWorker()); |
3512 | 0 | MOZ_DIAGNOSTIC_ASSERT(Type() != WorkerTypeService); |
3513 | 0 | { |
3514 | 0 | MutexAutoLock lock(mMutex); |
3515 | 0 | if (mStatus >= Canceling) { |
3516 | 0 | return; |
3517 | 0 | } |
3518 | 0 | } |
3519 | 0 | MOZ_DIAGNOSTIC_ASSERT(mClientSource); |
3520 | 0 |
|
3521 | 0 | if (IsBlobURI(mLoadInfo.mBaseURI)) { |
3522 | 0 | // Blob URL workers can only become controlled by inheriting from |
3523 | 0 | // their parent. Make sure to note this properly. |
3524 | 0 | mClientSource->InheritController(aServiceWorker); |
3525 | 0 | } else { |
3526 | 0 | // Otherwise this is a normal interception and we simply record the |
3527 | 0 | // controller locally. |
3528 | 0 | mClientSource->SetController(aServiceWorker); |
3529 | 0 | } |
3530 | 0 | } |
3531 | | |
3532 | | void |
3533 | | WorkerPrivate::ExecutionReady() |
3534 | 0 | { |
3535 | 0 | AssertIsOnWorkerThread(); |
3536 | 0 | { |
3537 | 0 | MutexAutoLock lock(mMutex); |
3538 | 0 | if (mStatus >= Canceling) { |
3539 | 0 | return; |
3540 | 0 | } |
3541 | 0 | } |
3542 | 0 | MOZ_DIAGNOSTIC_ASSERT(mClientSource); |
3543 | 0 | mClientSource->WorkerExecutionReady(this); |
3544 | 0 | } |
3545 | | |
3546 | | void |
3547 | | WorkerPrivate::InitializeGCTimers() |
3548 | 0 | { |
3549 | 0 | AssertIsOnWorkerThread(); |
3550 | 0 |
|
3551 | 0 | // We need a timer for GC. The basic plan is to run a non-shrinking GC |
3552 | 0 | // periodically (PERIODIC_GC_TIMER_DELAY_SEC) while the worker is running. |
3553 | 0 | // Once the worker goes idle we set a short (IDLE_GC_TIMER_DELAY_SEC) timer to |
3554 | 0 | // run a shrinking GC. If the worker receives more messages then the short |
3555 | 0 | // timer is canceled and the periodic timer resumes. |
3556 | 0 | mGCTimer = NS_NewTimer(); |
3557 | 0 | MOZ_ASSERT(mGCTimer); |
3558 | 0 |
|
3559 | 0 | mPeriodicGCTimerRunning = false; |
3560 | 0 | mIdleGCTimerRunning = false; |
3561 | 0 | } |
3562 | | |
3563 | | void |
3564 | | WorkerPrivate::SetGCTimerMode(GCTimerMode aMode) |
3565 | 0 | { |
3566 | 0 | AssertIsOnWorkerThread(); |
3567 | 0 | MOZ_ASSERT(mGCTimer); |
3568 | 0 |
|
3569 | 0 | if ((aMode == PeriodicTimer && mPeriodicGCTimerRunning) || |
3570 | 0 | (aMode == IdleTimer && mIdleGCTimerRunning)) { |
3571 | 0 | return; |
3572 | 0 | } |
3573 | 0 | |
3574 | 0 | MOZ_ALWAYS_SUCCEEDS(mGCTimer->Cancel()); |
3575 | 0 |
|
3576 | 0 | mPeriodicGCTimerRunning = false; |
3577 | 0 | mIdleGCTimerRunning = false; |
3578 | 0 | LOG(WorkerLog(), |
3579 | 0 | ("Worker %p canceled GC timer because %s\n", this, |
3580 | 0 | aMode == PeriodicTimer ? |
3581 | 0 | "periodic" : |
3582 | 0 | aMode == IdleTimer ? "idle" : "none")); |
3583 | 0 |
|
3584 | 0 | if (aMode == NoTimer) { |
3585 | 0 | return; |
3586 | 0 | } |
3587 | 0 | |
3588 | 0 | MOZ_ASSERT(aMode == PeriodicTimer || aMode == IdleTimer); |
3589 | 0 |
|
3590 | 0 | uint32_t delay = 0; |
3591 | 0 | int16_t type = nsITimer::TYPE_ONE_SHOT; |
3592 | 0 | nsTimerCallbackFunc callback = nullptr; |
3593 | 0 | const char* name = nullptr; |
3594 | 0 |
|
3595 | 0 | if (aMode == PeriodicTimer) { |
3596 | 0 | delay = PERIODIC_GC_TIMER_DELAY_SEC * 1000; |
3597 | 0 | type = nsITimer::TYPE_REPEATING_SLACK; |
3598 | 0 | callback = PeriodicGCTimerCallback; |
3599 | 0 | name = "dom::PeriodicGCTimerCallback"; |
3600 | 0 | } |
3601 | 0 | else { |
3602 | 0 | delay = IDLE_GC_TIMER_DELAY_SEC * 1000; |
3603 | 0 | type = nsITimer::TYPE_ONE_SHOT; |
3604 | 0 | callback = IdleGCTimerCallback; |
3605 | 0 | name = "dom::IdleGCTimerCallback"; |
3606 | 0 | } |
3607 | 0 |
|
3608 | 0 | MOZ_ALWAYS_SUCCEEDS(mGCTimer->SetTarget(mWorkerControlEventTarget)); |
3609 | 0 | MOZ_ALWAYS_SUCCEEDS( |
3610 | 0 | mGCTimer->InitWithNamedFuncCallback(callback, this, delay, type, name)); |
3611 | 0 |
|
3612 | 0 | if (aMode == PeriodicTimer) { |
3613 | 0 | LOG(WorkerLog(), ("Worker %p scheduled periodic GC timer\n", this)); |
3614 | 0 | mPeriodicGCTimerRunning = true; |
3615 | 0 | } |
3616 | 0 | else { |
3617 | 0 | LOG(WorkerLog(), ("Worker %p scheduled idle GC timer\n", this)); |
3618 | 0 | mIdleGCTimerRunning = true; |
3619 | 0 | } |
3620 | 0 | } |
3621 | | |
3622 | | void |
3623 | | WorkerPrivate::ShutdownGCTimers() |
3624 | 0 | { |
3625 | 0 | AssertIsOnWorkerThread(); |
3626 | 0 |
|
3627 | 0 | MOZ_ASSERT(mGCTimer); |
3628 | 0 |
|
3629 | 0 | // Always make sure the timer is canceled. |
3630 | 0 | MOZ_ALWAYS_SUCCEEDS(mGCTimer->Cancel()); |
3631 | 0 |
|
3632 | 0 | LOG(WorkerLog(), ("Worker %p killed the GC timer\n", this)); |
3633 | 0 |
|
3634 | 0 | mGCTimer = nullptr; |
3635 | 0 | mPeriodicGCTimerRunning = false; |
3636 | 0 | mIdleGCTimerRunning = false; |
3637 | 0 | } |
3638 | | |
3639 | | bool |
3640 | | WorkerPrivate::InterruptCallback(JSContext* aCx) |
3641 | 0 | { |
3642 | 0 | AssertIsOnWorkerThread(); |
3643 | 0 |
|
3644 | 0 | MOZ_ASSERT(!JS_IsExceptionPending(aCx)); |
3645 | 0 |
|
3646 | 0 | bool mayContinue = true; |
3647 | 0 | bool scheduledIdleGC = false; |
3648 | 0 |
|
3649 | 0 | for (;;) { |
3650 | 0 | // Run all control events now. |
3651 | 0 | auto result = ProcessAllControlRunnables(); |
3652 | 0 | if (result == ProcessAllControlRunnablesResult::Abort) { |
3653 | 0 | mayContinue = false; |
3654 | 0 | } |
3655 | 0 |
|
3656 | 0 | bool mayFreeze = mFrozen; |
3657 | 0 | if (mayFreeze) { |
3658 | 0 | MutexAutoLock lock(mMutex); |
3659 | 0 | mayFreeze = mStatus <= Running; |
3660 | 0 | } |
3661 | 0 |
|
3662 | 0 | if (!mayContinue || !mayFreeze) { |
3663 | 0 | break; |
3664 | 0 | } |
3665 | 0 | |
3666 | 0 | // Cancel the periodic GC timer here before freezing. The idle GC timer |
3667 | 0 | // will clean everything up once it runs. |
3668 | 0 | if (!scheduledIdleGC) { |
3669 | 0 | SetGCTimerMode(IdleTimer); |
3670 | 0 | scheduledIdleGC = true; |
3671 | 0 | } |
3672 | 0 |
|
3673 | 0 | while ((mayContinue = MayContinueRunning())) { |
3674 | 0 | MutexAutoLock lock(mMutex); |
3675 | 0 | if (!mControlQueue.IsEmpty()) { |
3676 | 0 | break; |
3677 | 0 | } |
3678 | 0 | |
3679 | 0 | WaitForWorkerEvents(); |
3680 | 0 | } |
3681 | 0 | } |
3682 | 0 |
|
3683 | 0 | if (!mayContinue) { |
3684 | 0 | // We want only uncatchable exceptions here. |
3685 | 0 | NS_ASSERTION(!JS_IsExceptionPending(aCx), |
3686 | 0 | "Should not have an exception set here!"); |
3687 | 0 | return false; |
3688 | 0 | } |
3689 | 0 |
|
3690 | 0 | // Make sure the periodic timer gets turned back on here. |
3691 | 0 | SetGCTimerMode(PeriodicTimer); |
3692 | 0 |
|
3693 | 0 | return true; |
3694 | 0 | } |
3695 | | |
3696 | | void |
3697 | | WorkerPrivate::CloseInternal() |
3698 | 0 | { |
3699 | 0 | AssertIsOnWorkerThread(); |
3700 | 0 | NotifyInternal(Closing); |
3701 | 0 | } |
3702 | | |
3703 | | bool |
3704 | | WorkerPrivate::IsOnCurrentThread() |
3705 | 0 | { |
3706 | 0 | // May be called on any thread! |
3707 | 0 |
|
3708 | 0 | MOZ_ASSERT(mPRThread); |
3709 | 0 | return PR_GetCurrentThread() == mPRThread; |
3710 | 0 | } |
3711 | | |
3712 | | void |
3713 | | WorkerPrivate::ScheduleDeletion(WorkerRanOrNot aRanOrNot) |
3714 | 0 | { |
3715 | 0 | AssertIsOnWorkerThread(); |
3716 | 0 | MOZ_ASSERT(mChildWorkers.IsEmpty()); |
3717 | 0 | MOZ_ASSERT(mSyncLoopStack.IsEmpty()); |
3718 | 0 | MOZ_ASSERT(!mPendingEventQueueClearing); |
3719 | 0 |
|
3720 | 0 | ClearMainEventQueue(aRanOrNot); |
3721 | | #ifdef DEBUG |
3722 | | if (WorkerRan == aRanOrNot) { |
3723 | | nsIThread* currentThread = NS_GetCurrentThread(); |
3724 | | MOZ_ASSERT(currentThread); |
3725 | | MOZ_ASSERT(!NS_HasPendingEvents(currentThread)); |
3726 | | } |
3727 | | #endif |
3728 | |
|
3729 | 0 | if (WorkerPrivate* parent = GetParent()) { |
3730 | 0 | RefPtr<WorkerFinishedRunnable> runnable = |
3731 | 0 | new WorkerFinishedRunnable(parent, this); |
3732 | 0 | if (!runnable->Dispatch()) { |
3733 | 0 | NS_WARNING("Failed to dispatch runnable!"); |
3734 | 0 | } |
3735 | 0 | } |
3736 | 0 | else { |
3737 | 0 | RefPtr<TopLevelWorkerFinishedRunnable> runnable = |
3738 | 0 | new TopLevelWorkerFinishedRunnable(this); |
3739 | 0 | if (NS_FAILED(DispatchToMainThread(runnable.forget()))) { |
3740 | 0 | NS_WARNING("Failed to dispatch runnable!"); |
3741 | 0 | } |
3742 | 0 | } |
3743 | 0 | } |
3744 | | |
3745 | | bool |
3746 | | WorkerPrivate::CollectRuntimeStats(JS::RuntimeStats* aRtStats, |
3747 | | bool aAnonymize) |
3748 | 0 | { |
3749 | 0 | AssertIsOnWorkerThread(); |
3750 | 0 | NS_ASSERTION(aRtStats, "Null RuntimeStats!"); |
3751 | 0 | NS_ASSERTION(mJSContext, "This must never be null!"); |
3752 | 0 |
|
3753 | 0 | return JS::CollectRuntimeStats(mJSContext, aRtStats, nullptr, aAnonymize); |
3754 | 0 | } |
3755 | | |
3756 | | void |
3757 | | WorkerPrivate::EnableMemoryReporter() |
3758 | 0 | { |
3759 | 0 | AssertIsOnWorkerThread(); |
3760 | 0 | MOZ_ASSERT(!mMemoryReporter); |
3761 | 0 |
|
3762 | 0 | // No need to lock here since the main thread can't race until we've |
3763 | 0 | // successfully registered the reporter. |
3764 | 0 | mMemoryReporter = new MemoryReporter(this); |
3765 | 0 |
|
3766 | 0 | if (NS_FAILED(RegisterWeakAsyncMemoryReporter(mMemoryReporter))) { |
3767 | 0 | NS_WARNING("Failed to register memory reporter!"); |
3768 | 0 | // No need to lock here since a failed registration means our memory |
3769 | 0 | // reporter can't start running. Just clean up. |
3770 | 0 | mMemoryReporter = nullptr; |
3771 | 0 | } |
3772 | 0 | } |
3773 | | |
3774 | | void |
3775 | | WorkerPrivate::DisableMemoryReporter() |
3776 | 0 | { |
3777 | 0 | AssertIsOnWorkerThread(); |
3778 | 0 |
|
3779 | 0 | RefPtr<MemoryReporter> memoryReporter; |
3780 | 0 | { |
3781 | 0 | // Mutex protectes MemoryReporter::mWorkerPrivate which is cleared by |
3782 | 0 | // MemoryReporter::Disable() below. |
3783 | 0 | MutexAutoLock lock(mMutex); |
3784 | 0 |
|
3785 | 0 | // There is nothing to do here if the memory reporter was never successfully |
3786 | 0 | // registered. |
3787 | 0 | if (!mMemoryReporter) { |
3788 | 0 | return; |
3789 | 0 | } |
3790 | 0 | |
3791 | 0 | // We don't need this set any longer. Swap it out so that we can unregister |
3792 | 0 | // below. |
3793 | 0 | mMemoryReporter.swap(memoryReporter); |
3794 | 0 |
|
3795 | 0 | // Next disable the memory reporter so that the main thread stops trying to |
3796 | 0 | // signal us. |
3797 | 0 | memoryReporter->Disable(); |
3798 | 0 | } |
3799 | 0 |
|
3800 | 0 | // Finally unregister the memory reporter. |
3801 | 0 | if (NS_FAILED(UnregisterWeakMemoryReporter(memoryReporter))) { |
3802 | 0 | NS_WARNING("Failed to unregister memory reporter!"); |
3803 | 0 | } |
3804 | 0 | } |
3805 | | |
3806 | | void |
3807 | | WorkerPrivate::WaitForWorkerEvents() |
3808 | 0 | { |
3809 | 0 | AUTO_PROFILER_LABEL("WorkerPrivate::WaitForWorkerEvents", IDLE); |
3810 | 0 |
|
3811 | 0 | AssertIsOnWorkerThread(); |
3812 | 0 | mMutex.AssertCurrentThreadOwns(); |
3813 | 0 |
|
3814 | 0 | // Wait for a worker event. |
3815 | 0 | mCondVar.Wait(); |
3816 | 0 | } |
3817 | | |
3818 | | WorkerPrivate::ProcessAllControlRunnablesResult |
3819 | | WorkerPrivate::ProcessAllControlRunnablesLocked() |
3820 | 0 | { |
3821 | 0 | AssertIsOnWorkerThread(); |
3822 | 0 | mMutex.AssertCurrentThreadOwns(); |
3823 | 0 |
|
3824 | 0 | auto result = ProcessAllControlRunnablesResult::Nothing; |
3825 | 0 |
|
3826 | 0 | for (;;) { |
3827 | 0 | WorkerControlRunnable* event; |
3828 | 0 | if (!mControlQueue.Pop(event)) { |
3829 | 0 | break; |
3830 | 0 | } |
3831 | 0 | |
3832 | 0 | MutexAutoUnlock unlock(mMutex); |
3833 | 0 |
|
3834 | 0 | MOZ_ASSERT(event); |
3835 | 0 | if (NS_FAILED(static_cast<nsIRunnable*>(event)->Run())) { |
3836 | 0 | result = ProcessAllControlRunnablesResult::Abort; |
3837 | 0 | } |
3838 | 0 |
|
3839 | 0 | if (result == ProcessAllControlRunnablesResult::Nothing) { |
3840 | 0 | // We ran at least one thing. |
3841 | 0 | result = ProcessAllControlRunnablesResult::MayContinue; |
3842 | 0 | } |
3843 | 0 | event->Release(); |
3844 | 0 | } |
3845 | 0 |
|
3846 | 0 | return result; |
3847 | 0 | } |
3848 | | |
3849 | | void |
3850 | | WorkerPrivate::ClearMainEventQueue(WorkerRanOrNot aRanOrNot) |
3851 | 0 | { |
3852 | 0 | AssertIsOnWorkerThread(); |
3853 | 0 |
|
3854 | 0 | MOZ_ASSERT(mSyncLoopStack.IsEmpty()); |
3855 | 0 | MOZ_ASSERT(!mCancelAllPendingRunnables); |
3856 | 0 | mCancelAllPendingRunnables = true; |
3857 | 0 |
|
3858 | 0 | if (WorkerNeverRan == aRanOrNot) { |
3859 | 0 | for (uint32_t count = mPreStartRunnables.Length(), index = 0; |
3860 | 0 | index < count; |
3861 | 0 | index++) { |
3862 | 0 | RefPtr<WorkerRunnable> runnable = mPreStartRunnables[index].forget(); |
3863 | 0 | static_cast<nsIRunnable*>(runnable.get())->Run(); |
3864 | 0 | } |
3865 | 0 | } else { |
3866 | 0 | nsIThread* currentThread = NS_GetCurrentThread(); |
3867 | 0 | MOZ_ASSERT(currentThread); |
3868 | 0 |
|
3869 | 0 | NS_ProcessPendingEvents(currentThread); |
3870 | 0 | } |
3871 | 0 |
|
3872 | 0 | MOZ_ASSERT(mCancelAllPendingRunnables); |
3873 | 0 | mCancelAllPendingRunnables = false; |
3874 | 0 | } |
3875 | | |
3876 | | void |
3877 | | WorkerPrivate::ClearDebuggerEventQueue() |
3878 | 0 | { |
3879 | 0 | while (!mDebuggerQueue.IsEmpty()) { |
3880 | 0 | WorkerRunnable* runnable = nullptr; |
3881 | 0 | mDebuggerQueue.Pop(runnable); |
3882 | 0 | // It should be ok to simply release the runnable, without running it. |
3883 | 0 | runnable->Release(); |
3884 | 0 | } |
3885 | 0 | } |
3886 | | |
3887 | | bool |
3888 | | WorkerPrivate::FreezeInternal() |
3889 | 0 | { |
3890 | 0 | AssertIsOnWorkerThread(); |
3891 | 0 |
|
3892 | 0 | NS_ASSERTION(!mFrozen, "Already frozen!"); |
3893 | 0 |
|
3894 | 0 | if (mClientSource) { |
3895 | 0 | mClientSource->Freeze(); |
3896 | 0 | } |
3897 | 0 |
|
3898 | 0 | mFrozen = true; |
3899 | 0 |
|
3900 | 0 | for (uint32_t index = 0; index < mChildWorkers.Length(); index++) { |
3901 | 0 | mChildWorkers[index]->Freeze(nullptr); |
3902 | 0 | } |
3903 | 0 |
|
3904 | 0 | return true; |
3905 | 0 | } |
3906 | | |
3907 | | bool |
3908 | | WorkerPrivate::ThawInternal() |
3909 | 0 | { |
3910 | 0 | AssertIsOnWorkerThread(); |
3911 | 0 |
|
3912 | 0 | NS_ASSERTION(mFrozen, "Not yet frozen!"); |
3913 | 0 |
|
3914 | 0 | for (uint32_t index = 0; index < mChildWorkers.Length(); index++) { |
3915 | 0 | mChildWorkers[index]->Thaw(nullptr); |
3916 | 0 | } |
3917 | 0 |
|
3918 | 0 | mFrozen = false; |
3919 | 0 |
|
3920 | 0 | if (mClientSource) { |
3921 | 0 | mClientSource->Thaw(); |
3922 | 0 | } |
3923 | 0 |
|
3924 | 0 | return true; |
3925 | 0 | } |
3926 | | |
3927 | | void |
3928 | | WorkerPrivate::PropagateFirstPartyStorageAccessGrantedInternal() |
3929 | 0 | { |
3930 | 0 | AssertIsOnWorkerThread(); |
3931 | 0 |
|
3932 | 0 | mLoadInfo.mFirstPartyStorageAccessGranted = true; |
3933 | 0 |
|
3934 | 0 | for (uint32_t index = 0; index < mChildWorkers.Length(); index++) { |
3935 | 0 | mChildWorkers[index]->PropagateFirstPartyStorageAccessGranted(); |
3936 | 0 | } |
3937 | 0 | } |
3938 | | |
3939 | | void |
3940 | | WorkerPrivate::TraverseTimeouts(nsCycleCollectionTraversalCallback& cb) |
3941 | 0 | { |
3942 | 0 | for (uint32_t i = 0; i < mTimeouts.Length(); ++i) { |
3943 | 0 | TimeoutInfo* tmp = mTimeouts[i]; |
3944 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHandler) |
3945 | 0 | } |
3946 | 0 | } |
3947 | | |
3948 | | void |
3949 | | WorkerPrivate::UnlinkTimeouts() |
3950 | 0 | { |
3951 | 0 | mTimeouts.Clear(); |
3952 | 0 | } |
3953 | | |
3954 | | bool |
3955 | | WorkerPrivate::ModifyBusyCountFromWorker(bool aIncrease) |
3956 | 0 | { |
3957 | 0 | AssertIsOnWorkerThread(); |
3958 | 0 |
|
3959 | 0 | { |
3960 | 0 | MutexAutoLock lock(mMutex); |
3961 | 0 |
|
3962 | 0 | // If we're in shutdown then the busy count is no longer being considered so |
3963 | 0 | // just return now. |
3964 | 0 | if (mStatus >= Killing) { |
3965 | 0 | return true; |
3966 | 0 | } |
3967 | 0 | } |
3968 | 0 | |
3969 | 0 | RefPtr<ModifyBusyCountRunnable> runnable = |
3970 | 0 | new ModifyBusyCountRunnable(this, aIncrease); |
3971 | 0 | return runnable->Dispatch(); |
3972 | 0 | } |
3973 | | |
3974 | | bool |
3975 | | WorkerPrivate::AddChildWorker(WorkerPrivate* aChildWorker) |
3976 | 0 | { |
3977 | 0 | AssertIsOnWorkerThread(); |
3978 | 0 |
|
3979 | | #ifdef DEBUG |
3980 | | { |
3981 | | WorkerStatus currentStatus; |
3982 | | { |
3983 | | MutexAutoLock lock(mMutex); |
3984 | | currentStatus = mStatus; |
3985 | | } |
3986 | | |
3987 | | MOZ_ASSERT(currentStatus == Running); |
3988 | | } |
3989 | | #endif |
3990 | |
|
3991 | 0 | NS_ASSERTION(!mChildWorkers.Contains(aChildWorker), |
3992 | 0 | "Already know about this one!"); |
3993 | 0 | mChildWorkers.AppendElement(aChildWorker); |
3994 | 0 |
|
3995 | 0 | return mChildWorkers.Length() == 1 ? |
3996 | 0 | ModifyBusyCountFromWorker(true) : |
3997 | 0 | true; |
3998 | 0 | } |
3999 | | |
4000 | | void |
4001 | | WorkerPrivate::RemoveChildWorker(WorkerPrivate* aChildWorker) |
4002 | 0 | { |
4003 | 0 | AssertIsOnWorkerThread(); |
4004 | 0 |
|
4005 | 0 | NS_ASSERTION(mChildWorkers.Contains(aChildWorker), |
4006 | 0 | "Didn't know about this one!"); |
4007 | 0 | mChildWorkers.RemoveElement(aChildWorker); |
4008 | 0 |
|
4009 | 0 | if (mChildWorkers.IsEmpty() && !ModifyBusyCountFromWorker(false)) { |
4010 | 0 | NS_WARNING("Failed to modify busy count!"); |
4011 | 0 | } |
4012 | 0 | } |
4013 | | |
4014 | | bool |
4015 | | WorkerPrivate::AddHolder(WorkerHolder* aHolder, WorkerStatus aFailStatus) |
4016 | 0 | { |
4017 | 0 | AssertIsOnWorkerThread(); |
4018 | 0 |
|
4019 | 0 | { |
4020 | 0 | MutexAutoLock lock(mMutex); |
4021 | 0 |
|
4022 | 0 | if (mStatus >= aFailStatus) { |
4023 | 0 | return false; |
4024 | 0 | } |
4025 | 0 | } |
4026 | 0 | |
4027 | 0 | MOZ_ASSERT(!mHolders.Contains(aHolder), "Already know about this one!"); |
4028 | 0 |
|
4029 | 0 | if (aHolder->GetBehavior() == WorkerHolder::PreventIdleShutdownStart) { |
4030 | 0 | if (!mNumHoldersPreventingShutdownStart && !ModifyBusyCountFromWorker(true)) { |
4031 | 0 | return false; |
4032 | 0 | } |
4033 | 0 | mNumHoldersPreventingShutdownStart += 1; |
4034 | 0 | } |
4035 | 0 |
|
4036 | 0 | mHolders.AppendElement(aHolder); |
4037 | 0 | return true; |
4038 | 0 | } |
4039 | | |
4040 | | void |
4041 | | WorkerPrivate::RemoveHolder(WorkerHolder* aHolder) |
4042 | 0 | { |
4043 | 0 | AssertIsOnWorkerThread(); |
4044 | 0 |
|
4045 | 0 | MOZ_ASSERT(mHolders.Contains(aHolder), "Didn't know about this one!"); |
4046 | 0 | mHolders.RemoveElement(aHolder); |
4047 | 0 |
|
4048 | 0 | if (aHolder->GetBehavior() == WorkerHolder::PreventIdleShutdownStart) { |
4049 | 0 | mNumHoldersPreventingShutdownStart -= 1; |
4050 | 0 | if (!mNumHoldersPreventingShutdownStart && !ModifyBusyCountFromWorker(false)) { |
4051 | 0 | NS_WARNING("Failed to modify busy count!"); |
4052 | 0 | } |
4053 | 0 | } |
4054 | 0 | } |
4055 | | |
4056 | | void |
4057 | | WorkerPrivate::NotifyHolders(WorkerStatus aStatus) |
4058 | 0 | { |
4059 | 0 | AssertIsOnWorkerThread(); |
4060 | 0 |
|
4061 | 0 | NS_ASSERTION(aStatus > Closing, "Bad status!"); |
4062 | 0 |
|
4063 | 0 | nsTObserverArray<WorkerHolder*>::ForwardIterator iter(mHolders); |
4064 | 0 | while (iter.HasMore()) { |
4065 | 0 | WorkerHolder* holder = iter.GetNext(); |
4066 | 0 | if (!holder->Notify(aStatus)) { |
4067 | 0 | NS_WARNING("Failed to notify holder!"); |
4068 | 0 | } |
4069 | 0 | } |
4070 | 0 |
|
4071 | 0 | AutoTArray<WorkerPrivate*, 10> children; |
4072 | 0 | children.AppendElements(mChildWorkers); |
4073 | 0 |
|
4074 | 0 | for (uint32_t index = 0; index < children.Length(); index++) { |
4075 | 0 | if (!children[index]->Notify(aStatus)) { |
4076 | 0 | NS_WARNING("Failed to notify child worker!"); |
4077 | 0 | } |
4078 | 0 | } |
4079 | 0 | } |
4080 | | |
4081 | | void |
4082 | | WorkerPrivate::CancelAllTimeouts() |
4083 | 0 | { |
4084 | 0 | AssertIsOnWorkerThread(); |
4085 | 0 |
|
4086 | 0 | LOG(TimeoutsLog(), ("Worker %p CancelAllTimeouts.\n", this)); |
4087 | 0 |
|
4088 | 0 | if (mTimerRunning) { |
4089 | 0 | NS_ASSERTION(mTimer && mTimerRunnable, "Huh?!"); |
4090 | 0 | NS_ASSERTION(!mTimeouts.IsEmpty(), "Huh?!"); |
4091 | 0 |
|
4092 | 0 | if (NS_FAILED(mTimer->Cancel())) { |
4093 | 0 | NS_WARNING("Failed to cancel timer!"); |
4094 | 0 | } |
4095 | 0 |
|
4096 | 0 | for (uint32_t index = 0; index < mTimeouts.Length(); index++) { |
4097 | 0 | mTimeouts[index]->mCanceled = true; |
4098 | 0 | } |
4099 | 0 |
|
4100 | 0 | // If mRunningExpiredTimeouts, then the fact that they are all canceled now |
4101 | 0 | // means that the currently executing RunExpiredTimeouts will deal with |
4102 | 0 | // them. Otherwise, we need to clean them up ourselves. |
4103 | 0 | if (!mRunningExpiredTimeouts) { |
4104 | 0 | mTimeouts.Clear(); |
4105 | 0 | ModifyBusyCountFromWorker(false); |
4106 | 0 | } |
4107 | 0 |
|
4108 | 0 | // Set mTimerRunning false even if mRunningExpiredTimeouts is true, so that |
4109 | 0 | // if we get reentered under this same RunExpiredTimeouts call we don't |
4110 | 0 | // assert above that !mTimeouts().IsEmpty(), because that's clearly false |
4111 | 0 | // now. |
4112 | 0 | mTimerRunning = false; |
4113 | 0 | } |
4114 | | #ifdef DEBUG |
4115 | | else if (!mRunningExpiredTimeouts) { |
4116 | | NS_ASSERTION(mTimeouts.IsEmpty(), "Huh?!"); |
4117 | | } |
4118 | | #endif |
4119 | |
|
4120 | 0 | mTimer = nullptr; |
4121 | 0 | mTimerRunnable = nullptr; |
4122 | 0 | } |
4123 | | |
4124 | | already_AddRefed<nsIEventTarget> |
4125 | | WorkerPrivate::CreateNewSyncLoop(WorkerStatus aFailStatus) |
4126 | 0 | { |
4127 | 0 | AssertIsOnWorkerThread(); |
4128 | 0 | MOZ_ASSERT(aFailStatus >= Canceling, |
4129 | 0 | "Sync loops can be created when the worker is in Running/Closing state!"); |
4130 | 0 |
|
4131 | 0 | { |
4132 | 0 | MutexAutoLock lock(mMutex); |
4133 | 0 |
|
4134 | 0 | if (mStatus >= aFailStatus) { |
4135 | 0 | return nullptr; |
4136 | 0 | } |
4137 | 0 | } |
4138 | 0 | |
4139 | 0 | auto queue = static_cast<ThreadEventQueue<EventQueue>*>(mThread->EventQueue()); |
4140 | 0 | nsCOMPtr<nsISerialEventTarget> realEventTarget = queue->PushEventQueue(); |
4141 | 0 | MOZ_ASSERT(realEventTarget); |
4142 | 0 |
|
4143 | 0 | RefPtr<EventTarget> workerEventTarget = |
4144 | 0 | new EventTarget(this, realEventTarget); |
4145 | 0 |
|
4146 | 0 | { |
4147 | 0 | // Modifications must be protected by mMutex in DEBUG builds, see comment |
4148 | 0 | // about mSyncLoopStack in WorkerPrivate.h. |
4149 | | #ifdef DEBUG |
4150 | | MutexAutoLock lock(mMutex); |
4151 | | #endif |
4152 | |
|
4153 | 0 | mSyncLoopStack.AppendElement(new SyncLoopInfo(workerEventTarget)); |
4154 | 0 | } |
4155 | 0 |
|
4156 | 0 | return workerEventTarget.forget(); |
4157 | 0 | } |
4158 | | |
4159 | | bool |
4160 | | WorkerPrivate::RunCurrentSyncLoop() |
4161 | 0 | { |
4162 | 0 | AssertIsOnWorkerThread(); |
4163 | 0 |
|
4164 | 0 | JSContext* cx = GetJSContext(); |
4165 | 0 | MOZ_ASSERT(cx); |
4166 | 0 |
|
4167 | 0 | // This should not change between now and the time we finish running this sync |
4168 | 0 | // loop. |
4169 | 0 | uint32_t currentLoopIndex = mSyncLoopStack.Length() - 1; |
4170 | 0 |
|
4171 | 0 | SyncLoopInfo* loopInfo = mSyncLoopStack[currentLoopIndex]; |
4172 | 0 |
|
4173 | 0 | MOZ_ASSERT(loopInfo); |
4174 | 0 | MOZ_ASSERT(!loopInfo->mHasRun); |
4175 | 0 | MOZ_ASSERT(!loopInfo->mCompleted); |
4176 | 0 |
|
4177 | | #ifdef DEBUG |
4178 | | loopInfo->mHasRun = true; |
4179 | | #endif |
4180 | |
|
4181 | 0 | while (!loopInfo->mCompleted) { |
4182 | 0 | bool normalRunnablesPending = false; |
4183 | 0 |
|
4184 | 0 | // Don't block with the periodic GC timer running. |
4185 | 0 | if (!NS_HasPendingEvents(mThread)) { |
4186 | 0 | SetGCTimerMode(IdleTimer); |
4187 | 0 | } |
4188 | 0 |
|
4189 | 0 | // Wait for something to do. |
4190 | 0 | { |
4191 | 0 | MutexAutoLock lock(mMutex); |
4192 | 0 |
|
4193 | 0 | for (;;) { |
4194 | 0 | while (mControlQueue.IsEmpty() && |
4195 | 0 | !normalRunnablesPending && |
4196 | 0 | !(normalRunnablesPending = NS_HasPendingEvents(mThread))) { |
4197 | 0 | WaitForWorkerEvents(); |
4198 | 0 | } |
4199 | 0 |
|
4200 | 0 | auto result = ProcessAllControlRunnablesLocked(); |
4201 | 0 | if (result != ProcessAllControlRunnablesResult::Nothing) { |
4202 | 0 | // XXXkhuey how should we handle Abort here? See Bug 1003730. |
4203 | 0 |
|
4204 | 0 | // The state of the world may have changed. Recheck it. |
4205 | 0 | normalRunnablesPending = NS_HasPendingEvents(mThread); |
4206 | 0 |
|
4207 | 0 | // NB: If we processed a NotifyRunnable, we might have run |
4208 | 0 | // non-control runnables, one of which may have shut down the |
4209 | 0 | // sync loop. |
4210 | 0 | if (loopInfo->mCompleted) { |
4211 | 0 | break; |
4212 | 0 | } |
4213 | 0 | } |
4214 | 0 | |
4215 | 0 | // If we *didn't* run any control runnables, this should be unchanged. |
4216 | 0 | MOZ_ASSERT(!loopInfo->mCompleted); |
4217 | 0 |
|
4218 | 0 | if (normalRunnablesPending) { |
4219 | 0 | break; |
4220 | 0 | } |
4221 | 0 | } |
4222 | 0 | } |
4223 | 0 |
|
4224 | 0 | if (normalRunnablesPending) { |
4225 | 0 | // Make sure the periodic timer is running before we continue. |
4226 | 0 | SetGCTimerMode(PeriodicTimer); |
4227 | 0 |
|
4228 | 0 | MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(mThread, false)); |
4229 | 0 |
|
4230 | 0 | // Now *might* be a good time to GC. Let the JS engine make the decision. |
4231 | 0 | if (JS::CurrentGlobalOrNull(cx)) { |
4232 | 0 | JS_MaybeGC(cx); |
4233 | 0 | } |
4234 | 0 | } |
4235 | 0 | } |
4236 | 0 |
|
4237 | 0 | // Make sure that the stack didn't change underneath us. |
4238 | 0 | MOZ_ASSERT(mSyncLoopStack[currentLoopIndex] == loopInfo); |
4239 | 0 |
|
4240 | 0 | return DestroySyncLoop(currentLoopIndex); |
4241 | 0 | } |
4242 | | |
4243 | | bool |
4244 | | WorkerPrivate::DestroySyncLoop(uint32_t aLoopIndex) |
4245 | 0 | { |
4246 | 0 | MOZ_ASSERT(!mSyncLoopStack.IsEmpty()); |
4247 | 0 | MOZ_ASSERT(mSyncLoopStack.Length() - 1 == aLoopIndex); |
4248 | 0 |
|
4249 | 0 | // We're about to delete the loop, stash its event target and result. |
4250 | 0 | SyncLoopInfo* loopInfo = mSyncLoopStack[aLoopIndex]; |
4251 | 0 | nsIEventTarget* nestedEventTarget = |
4252 | 0 | loopInfo->mEventTarget->GetWeakNestedEventTarget(); |
4253 | 0 | MOZ_ASSERT(nestedEventTarget); |
4254 | 0 |
|
4255 | 0 | bool result = loopInfo->mResult; |
4256 | 0 |
|
4257 | 0 | { |
4258 | 0 | // Modifications must be protected by mMutex in DEBUG builds, see comment |
4259 | 0 | // about mSyncLoopStack in WorkerPrivate.h. |
4260 | | #ifdef DEBUG |
4261 | | MutexAutoLock lock(mMutex); |
4262 | | #endif |
4263 | |
|
4264 | 0 | // This will delete |loopInfo|! |
4265 | 0 | mSyncLoopStack.RemoveElementAt(aLoopIndex); |
4266 | 0 | } |
4267 | 0 |
|
4268 | 0 | auto queue = static_cast<ThreadEventQueue<EventQueue>*>(mThread->EventQueue()); |
4269 | 0 | queue->PopEventQueue(nestedEventTarget); |
4270 | 0 |
|
4271 | 0 | if (mSyncLoopStack.IsEmpty() && mPendingEventQueueClearing) { |
4272 | 0 | mPendingEventQueueClearing = false; |
4273 | 0 | ClearMainEventQueue(WorkerRan); |
4274 | 0 | } |
4275 | 0 |
|
4276 | 0 | return result; |
4277 | 0 | } |
4278 | | |
4279 | | void |
4280 | | WorkerPrivate::StopSyncLoop(nsIEventTarget* aSyncLoopTarget, bool aResult) |
4281 | 0 | { |
4282 | 0 | AssertIsOnWorkerThread(); |
4283 | 0 | AssertValidSyncLoop(aSyncLoopTarget); |
4284 | 0 |
|
4285 | 0 | MOZ_ASSERT(!mSyncLoopStack.IsEmpty()); |
4286 | 0 |
|
4287 | 0 | for (uint32_t index = mSyncLoopStack.Length(); index > 0; index--) { |
4288 | 0 | nsAutoPtr<SyncLoopInfo>& loopInfo = mSyncLoopStack[index - 1]; |
4289 | 0 | MOZ_ASSERT(loopInfo); |
4290 | 0 | MOZ_ASSERT(loopInfo->mEventTarget); |
4291 | 0 |
|
4292 | 0 | if (loopInfo->mEventTarget == aSyncLoopTarget) { |
4293 | 0 | // Can't assert |loop->mHasRun| here because dispatch failures can cause |
4294 | 0 | // us to bail out early. |
4295 | 0 | MOZ_ASSERT(!loopInfo->mCompleted); |
4296 | 0 |
|
4297 | 0 | loopInfo->mResult = aResult; |
4298 | 0 | loopInfo->mCompleted = true; |
4299 | 0 |
|
4300 | 0 | loopInfo->mEventTarget->Disable(); |
4301 | 0 |
|
4302 | 0 | return; |
4303 | 0 | } |
4304 | 0 |
|
4305 | 0 | MOZ_ASSERT(!SameCOMIdentity(loopInfo->mEventTarget, aSyncLoopTarget)); |
4306 | 0 | } |
4307 | 0 |
|
4308 | 0 | MOZ_CRASH("Unknown sync loop!"); |
4309 | 0 | } |
4310 | | |
4311 | | #ifdef DEBUG |
4312 | | void |
4313 | | WorkerPrivate::AssertValidSyncLoop(nsIEventTarget* aSyncLoopTarget) |
4314 | | { |
4315 | | MOZ_ASSERT(aSyncLoopTarget); |
4316 | | |
4317 | | EventTarget* workerTarget; |
4318 | | nsresult rv = |
4319 | | aSyncLoopTarget->QueryInterface(kDEBUGWorkerEventTargetIID, |
4320 | | reinterpret_cast<void**>(&workerTarget)); |
4321 | | MOZ_ASSERT(NS_SUCCEEDED(rv)); |
4322 | | MOZ_ASSERT(workerTarget); |
4323 | | |
4324 | | bool valid = false; |
4325 | | |
4326 | | { |
4327 | | MutexAutoLock lock(mMutex); |
4328 | | |
4329 | | for (uint32_t index = 0; index < mSyncLoopStack.Length(); index++) { |
4330 | | nsAutoPtr<SyncLoopInfo>& loopInfo = mSyncLoopStack[index]; |
4331 | | MOZ_ASSERT(loopInfo); |
4332 | | MOZ_ASSERT(loopInfo->mEventTarget); |
4333 | | |
4334 | | if (loopInfo->mEventTarget == aSyncLoopTarget) { |
4335 | | valid = true; |
4336 | | break; |
4337 | | } |
4338 | | |
4339 | | MOZ_ASSERT(!SameCOMIdentity(loopInfo->mEventTarget, aSyncLoopTarget)); |
4340 | | } |
4341 | | } |
4342 | | |
4343 | | MOZ_ASSERT(valid); |
4344 | | } |
4345 | | #endif |
4346 | | |
4347 | | void |
4348 | | WorkerPrivate::PostMessageToParent( |
4349 | | JSContext* aCx, |
4350 | | JS::Handle<JS::Value> aMessage, |
4351 | | const Sequence<JSObject*>& aTransferable, |
4352 | | ErrorResult& aRv) |
4353 | 0 | { |
4354 | 0 | AssertIsOnWorkerThread(); |
4355 | 0 |
|
4356 | 0 | JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue()); |
4357 | 0 |
|
4358 | 0 | aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransferable, |
4359 | 0 | &transferable); |
4360 | 0 | if (NS_WARN_IF(aRv.Failed())) { |
4361 | 0 | return; |
4362 | 0 | } |
4363 | 0 | |
4364 | 0 | RefPtr<MessageEventRunnable> runnable = |
4365 | 0 | new MessageEventRunnable(this, |
4366 | 0 | WorkerRunnable::ParentThreadUnchangedBusyCount); |
4367 | 0 |
|
4368 | 0 | UniquePtr<AbstractTimelineMarker> start; |
4369 | 0 | UniquePtr<AbstractTimelineMarker> end; |
4370 | 0 | RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); |
4371 | 0 | bool isTimelineRecording = timelines && !timelines->IsEmpty(); |
4372 | 0 |
|
4373 | 0 | if (isTimelineRecording) { |
4374 | 0 | start = MakeUnique<WorkerTimelineMarker>(NS_IsMainThread() |
4375 | 0 | ? ProfileTimelineWorkerOperationType::SerializeDataOnMainThread |
4376 | 0 | : ProfileTimelineWorkerOperationType::SerializeDataOffMainThread, |
4377 | 0 | MarkerTracingType::START); |
4378 | 0 | } |
4379 | 0 |
|
4380 | 0 | runnable->Write(aCx, aMessage, transferable, JS::CloneDataPolicy(), aRv); |
4381 | 0 |
|
4382 | 0 | if (isTimelineRecording) { |
4383 | 0 | end = MakeUnique<WorkerTimelineMarker>(NS_IsMainThread() |
4384 | 0 | ? ProfileTimelineWorkerOperationType::SerializeDataOnMainThread |
4385 | 0 | : ProfileTimelineWorkerOperationType::SerializeDataOffMainThread, |
4386 | 0 | MarkerTracingType::END); |
4387 | 0 | timelines->AddMarkerForAllObservedDocShells(start); |
4388 | 0 | timelines->AddMarkerForAllObservedDocShells(end); |
4389 | 0 | } |
4390 | 0 |
|
4391 | 0 | if (NS_WARN_IF(aRv.Failed())) { |
4392 | 0 | return; |
4393 | 0 | } |
4394 | 0 | |
4395 | 0 | if (!runnable->Dispatch()) { |
4396 | 0 | aRv = NS_ERROR_FAILURE; |
4397 | 0 | } |
4398 | 0 | } |
4399 | | |
4400 | | void |
4401 | | WorkerPrivate::EnterDebuggerEventLoop() |
4402 | 0 | { |
4403 | 0 | AssertIsOnWorkerThread(); |
4404 | 0 |
|
4405 | 0 | JSContext* cx = GetJSContext(); |
4406 | 0 | MOZ_ASSERT(cx); |
4407 | 0 | CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get(); |
4408 | 0 |
|
4409 | 0 | uint32_t currentEventLoopLevel = ++mDebuggerEventLoopLevel; |
4410 | 0 |
|
4411 | 0 | while (currentEventLoopLevel <= mDebuggerEventLoopLevel) { |
4412 | 0 |
|
4413 | 0 | bool debuggerRunnablesPending = false; |
4414 | 0 |
|
4415 | 0 | { |
4416 | 0 | MutexAutoLock lock(mMutex); |
4417 | 0 |
|
4418 | 0 | debuggerRunnablesPending = !mDebuggerQueue.IsEmpty(); |
4419 | 0 | } |
4420 | 0 |
|
4421 | 0 | // Don't block with the periodic GC timer running. |
4422 | 0 | if (!debuggerRunnablesPending) { |
4423 | 0 | SetGCTimerMode(IdleTimer); |
4424 | 0 | } |
4425 | 0 |
|
4426 | 0 | // Wait for something to do |
4427 | 0 | { |
4428 | 0 | MutexAutoLock lock(mMutex); |
4429 | 0 |
|
4430 | 0 | std::queue<RefPtr<MicroTaskRunnable>>& debuggerMtQueue = |
4431 | 0 | ccjscx->GetDebuggerMicroTaskQueue(); |
4432 | 0 | while (mControlQueue.IsEmpty() && |
4433 | 0 | !(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty()) && |
4434 | 0 | debuggerMtQueue.empty()) { |
4435 | 0 | WaitForWorkerEvents(); |
4436 | 0 | } |
4437 | 0 |
|
4438 | 0 | ProcessAllControlRunnablesLocked(); |
4439 | 0 |
|
4440 | 0 | // XXXkhuey should we abort JS on the stack here if we got Abort above? |
4441 | 0 | } |
4442 | 0 | ccjscx->PerformDebuggerMicroTaskCheckpoint(); |
4443 | 0 | if (debuggerRunnablesPending) { |
4444 | 0 | // Start the periodic GC timer if it is not already running. |
4445 | 0 | SetGCTimerMode(PeriodicTimer); |
4446 | 0 |
|
4447 | 0 | WorkerRunnable* runnable = nullptr; |
4448 | 0 |
|
4449 | 0 | { |
4450 | 0 | MutexAutoLock lock(mMutex); |
4451 | 0 |
|
4452 | 0 | mDebuggerQueue.Pop(runnable); |
4453 | 0 | } |
4454 | 0 |
|
4455 | 0 | MOZ_ASSERT(runnable); |
4456 | 0 | static_cast<nsIRunnable*>(runnable)->Run(); |
4457 | 0 | runnable->Release(); |
4458 | 0 |
|
4459 | 0 | ccjscx->PerformDebuggerMicroTaskCheckpoint(); |
4460 | 0 |
|
4461 | 0 | // Now *might* be a good time to GC. Let the JS engine make the decision. |
4462 | 0 | if (JS::CurrentGlobalOrNull(cx)) { |
4463 | 0 | JS_MaybeGC(cx); |
4464 | 0 | } |
4465 | 0 | } |
4466 | 0 | } |
4467 | 0 | } |
4468 | | |
4469 | | void |
4470 | | WorkerPrivate::LeaveDebuggerEventLoop() |
4471 | 0 | { |
4472 | 0 | AssertIsOnWorkerThread(); |
4473 | 0 |
|
4474 | 0 | MutexAutoLock lock(mMutex); |
4475 | 0 |
|
4476 | 0 | if (mDebuggerEventLoopLevel > 0) { |
4477 | 0 | --mDebuggerEventLoopLevel; |
4478 | 0 | } |
4479 | 0 | } |
4480 | | |
4481 | | void |
4482 | | WorkerPrivate::PostMessageToDebugger(const nsAString& aMessage) |
4483 | 0 | { |
4484 | 0 | mDebugger->PostMessageToDebugger(aMessage); |
4485 | 0 | } |
4486 | | |
4487 | | void |
4488 | | WorkerPrivate::SetDebuggerImmediate(dom::Function& aHandler, ErrorResult& aRv) |
4489 | 0 | { |
4490 | 0 | AssertIsOnWorkerThread(); |
4491 | 0 |
|
4492 | 0 | RefPtr<DebuggerImmediateRunnable> runnable = |
4493 | 0 | new DebuggerImmediateRunnable(this, aHandler); |
4494 | 0 | if (!runnable->Dispatch()) { |
4495 | 0 | aRv.Throw(NS_ERROR_FAILURE); |
4496 | 0 | } |
4497 | 0 | } |
4498 | | |
4499 | | void |
4500 | | WorkerPrivate::ReportErrorToDebugger(const nsAString& aFilename, |
4501 | | uint32_t aLineno, |
4502 | | const nsAString& aMessage) |
4503 | 0 | { |
4504 | 0 | mDebugger->ReportErrorToDebugger(aFilename, aLineno, aMessage); |
4505 | 0 | } |
4506 | | |
4507 | | bool |
4508 | | WorkerPrivate::NotifyInternal(WorkerStatus aStatus) |
4509 | 0 | { |
4510 | 0 | AssertIsOnWorkerThread(); |
4511 | 0 |
|
4512 | 0 | NS_ASSERTION(aStatus > Running && aStatus < Dead, "Bad status!"); |
4513 | 0 |
|
4514 | 0 | RefPtr<EventTarget> eventTarget; |
4515 | 0 |
|
4516 | 0 | // Save the old status and set the new status. |
4517 | 0 | WorkerStatus previousStatus; |
4518 | 0 | { |
4519 | 0 | MutexAutoLock lock(mMutex); |
4520 | 0 |
|
4521 | 0 | if (mStatus >= aStatus) { |
4522 | 0 | return true; |
4523 | 0 | } |
4524 | 0 | |
4525 | 0 | if (aStatus >= Canceling) { |
4526 | 0 | MutexAutoUnlock unlock(mMutex); |
4527 | 0 | mClientSource.reset(); |
4528 | 0 | if (mScope) { |
4529 | 0 | mScope->NoteTerminating(); |
4530 | 0 | } |
4531 | 0 | } |
4532 | 0 |
|
4533 | 0 | // Make sure the hybrid event target stops dispatching runnables |
4534 | 0 | // once we reaching the killing state. |
4535 | 0 | if (aStatus == Killing) { |
4536 | 0 | // To avoid deadlock we always acquire the event target mutex before the |
4537 | 0 | // worker private mutex. (We do it in this order because this is what |
4538 | 0 | // workers best for event dispatching.) To enforce that order here we |
4539 | 0 | // need to unlock the worker private mutex before we lock the event target |
4540 | 0 | // mutex in ForgetWorkerPrivate. |
4541 | 0 | { |
4542 | 0 | MutexAutoUnlock unlock(mMutex); |
4543 | 0 | mWorkerHybridEventTarget->ForgetWorkerPrivate(this); |
4544 | 0 | } |
4545 | 0 |
|
4546 | 0 | // Check the status code again in case another NotifyInternal came in |
4547 | 0 | // while we were unlocked above. |
4548 | 0 | if (mStatus >= aStatus) { |
4549 | 0 | return true; |
4550 | 0 | } |
4551 | 0 | } |
4552 | 0 | |
4553 | 0 | previousStatus = mStatus; |
4554 | 0 | mStatus = aStatus; |
4555 | 0 |
|
4556 | 0 | // Mark parent status as closing immediately to avoid new events being |
4557 | 0 | // dispatched after we clear the queue below. |
4558 | 0 | if (aStatus == Closing) { |
4559 | 0 | Close(); |
4560 | 0 | } |
4561 | 0 | } |
4562 | 0 |
|
4563 | 0 | MOZ_ASSERT(previousStatus != Pending); |
4564 | 0 |
|
4565 | 0 | if (aStatus >= Closing) { |
4566 | 0 | CancelAllTimeouts(); |
4567 | 0 | } |
4568 | 0 |
|
4569 | 0 | // Let all our holders know the new status. |
4570 | 0 | if (aStatus > Closing) { |
4571 | 0 | NotifyHolders(aStatus); |
4572 | 0 | } |
4573 | 0 |
|
4574 | 0 | // If this is the first time our status has changed then we need to clear the |
4575 | 0 | // main event queue. |
4576 | 0 | if (previousStatus == Running) { |
4577 | 0 | // NB: If we're in a sync loop, we can't clear the queue immediately, |
4578 | 0 | // because this is the wrong queue. So we have to defer it until later. |
4579 | 0 | if (!mSyncLoopStack.IsEmpty()) { |
4580 | 0 | mPendingEventQueueClearing = true; |
4581 | 0 | } else { |
4582 | 0 | ClearMainEventQueue(WorkerRan); |
4583 | 0 | } |
4584 | 0 | } |
4585 | 0 |
|
4586 | 0 | // If the worker script never ran, or failed to compile, we don't need to do |
4587 | 0 | // anything else. |
4588 | 0 | if (!GlobalScope()) { |
4589 | 0 | return true; |
4590 | 0 | } |
4591 | 0 | |
4592 | 0 | // Don't abort the script now, but we dispatch a runnable to do it when the |
4593 | 0 | // current JS frame is executed. |
4594 | 0 | if (aStatus == Closing) { |
4595 | 0 | if (mSyncLoopStack.IsEmpty()) { |
4596 | 0 | // Here we use a normal runnable to know when the current JS chunk of code |
4597 | 0 | // is finished. We cannot use a WorkerRunnable because they are not |
4598 | 0 | // accepted any more by the worker, and we do not want to use a |
4599 | 0 | // WorkerControlRunnable because they are immediately executed. |
4600 | 0 | RefPtr<CancelingRunnable> r = new CancelingRunnable(); |
4601 | 0 | mThread->nsThread::Dispatch(r.forget(), NS_DISPATCH_NORMAL); |
4602 | 0 |
|
4603 | 0 | // At the same time, we want to be sure that we interrupt infinite loops. |
4604 | 0 | // The following runnable starts a timer that cancel the worker, from the |
4605 | 0 | // parent thread, after CANCELING_TIMEOUT millseconds. |
4606 | 0 | RefPtr<CancelingWithTimeoutOnParentRunnable> rr = |
4607 | 0 | new CancelingWithTimeoutOnParentRunnable(this); |
4608 | 0 | rr->Dispatch(); |
4609 | 0 | } |
4610 | 0 | return true; |
4611 | 0 | } |
4612 | 0 |
|
4613 | 0 | MOZ_ASSERT(aStatus == Canceling || aStatus == Killing); |
4614 | 0 |
|
4615 | 0 | // Always abort the script. |
4616 | 0 | return false; |
4617 | 0 | } |
4618 | | |
4619 | | void |
4620 | | WorkerPrivate::ReportError(JSContext* aCx, JS::ConstUTF8CharsZ aToStringResult, |
4621 | | JSErrorReport* aReport) |
4622 | 0 | { |
4623 | 0 | AssertIsOnWorkerThread(); |
4624 | 0 |
|
4625 | 0 | if (!MayContinueRunning() || mErrorHandlerRecursionCount == 2) { |
4626 | 0 | return; |
4627 | 0 | } |
4628 | 0 | |
4629 | 0 | NS_ASSERTION(mErrorHandlerRecursionCount == 0 || |
4630 | 0 | mErrorHandlerRecursionCount == 1, |
4631 | 0 | "Bad recursion logic!"); |
4632 | 0 |
|
4633 | 0 | JS::Rooted<JS::Value> exn(aCx); |
4634 | 0 | if (!JS_GetPendingException(aCx, &exn)) { |
4635 | 0 | // Probably shouldn't actually happen? But let's go ahead and just use null |
4636 | 0 | // for lack of anything better. |
4637 | 0 | exn.setNull(); |
4638 | 0 | } |
4639 | 0 | JS_ClearPendingException(aCx); |
4640 | 0 |
|
4641 | 0 | WorkerErrorReport report; |
4642 | 0 | if (aReport) { |
4643 | 0 | report.AssignErrorReport(aReport); |
4644 | 0 | } |
4645 | 0 | else { |
4646 | 0 | report.mFlags = nsIScriptError::errorFlag | nsIScriptError::exceptionFlag; |
4647 | 0 | } |
4648 | 0 |
|
4649 | 0 | if (report.mMessage.IsEmpty() && aToStringResult) { |
4650 | 0 | nsDependentCString toStringResult(aToStringResult.c_str()); |
4651 | 0 | if (!AppendUTF8toUTF16(toStringResult, report.mMessage, mozilla::fallible)) { |
4652 | 0 | // Try again, with only a 1 KB string. Do this infallibly this time. |
4653 | 0 | // If the user doesn't have 1 KB to spare we're done anyways. |
4654 | 0 | uint32_t index = std::min(uint32_t(1024), toStringResult.Length()); |
4655 | 0 |
|
4656 | 0 | // Drop the last code point that may be cropped. |
4657 | 0 | index = RewindToPriorUTF8Codepoint(toStringResult.BeginReading(), index); |
4658 | 0 |
|
4659 | 0 | nsDependentCString truncatedToStringResult(aToStringResult.c_str(), |
4660 | 0 | index); |
4661 | 0 | AppendUTF8toUTF16(truncatedToStringResult, report.mMessage); |
4662 | 0 | } |
4663 | 0 | } |
4664 | 0 |
|
4665 | 0 | mErrorHandlerRecursionCount++; |
4666 | 0 |
|
4667 | 0 | // Don't want to run the scope's error handler if this is a recursive error or |
4668 | 0 | // if we ran out of memory. |
4669 | 0 | bool fireAtScope = mErrorHandlerRecursionCount == 1 && |
4670 | 0 | report.mErrorNumber != JSMSG_OUT_OF_MEMORY && |
4671 | 0 | JS::CurrentGlobalOrNull(aCx); |
4672 | 0 |
|
4673 | 0 | WorkerErrorReport::ReportError(aCx, this, fireAtScope, nullptr, report, 0, |
4674 | 0 | exn); |
4675 | 0 |
|
4676 | 0 | mErrorHandlerRecursionCount--; |
4677 | 0 | } |
4678 | | |
4679 | | // static |
4680 | | void |
4681 | | WorkerPrivate::ReportErrorToConsole(const char* aMessage) |
4682 | 0 | { |
4683 | 0 | nsTArray<nsString> emptyParams; |
4684 | 0 | WorkerPrivate::ReportErrorToConsole(aMessage, emptyParams); |
4685 | 0 | } |
4686 | | |
4687 | | // static |
4688 | | void |
4689 | | WorkerPrivate::ReportErrorToConsole(const char* aMessage, |
4690 | | const nsTArray<nsString>& aParams) |
4691 | 0 | { |
4692 | 0 | WorkerPrivate* wp = nullptr; |
4693 | 0 | if (!NS_IsMainThread()) { |
4694 | 0 | wp = GetCurrentThreadWorkerPrivate(); |
4695 | 0 | } |
4696 | 0 |
|
4697 | 0 | ReportErrorToConsoleRunnable::Report(wp, aMessage, aParams); |
4698 | 0 | } |
4699 | | |
4700 | | int32_t |
4701 | | WorkerPrivate::SetTimeout(JSContext* aCx, |
4702 | | nsIScriptTimeoutHandler* aHandler, |
4703 | | int32_t aTimeout, bool aIsInterval, |
4704 | | ErrorResult& aRv) |
4705 | 0 | { |
4706 | 0 | AssertIsOnWorkerThread(); |
4707 | 0 | MOZ_ASSERT(aHandler); |
4708 | 0 |
|
4709 | 0 | const int32_t timerId = mNextTimeoutId++; |
4710 | 0 |
|
4711 | 0 | WorkerStatus currentStatus; |
4712 | 0 | { |
4713 | 0 | MutexAutoLock lock(mMutex); |
4714 | 0 | currentStatus = mStatus; |
4715 | 0 | } |
4716 | 0 |
|
4717 | 0 | // If the worker is trying to call setTimeout/setInterval and the parent |
4718 | 0 | // thread has initiated the close process then just silently fail. |
4719 | 0 | if (currentStatus >= Closing) { |
4720 | 0 | return timerId; |
4721 | 0 | } |
4722 | 0 | |
4723 | 0 | nsAutoPtr<TimeoutInfo> newInfo(new TimeoutInfo()); |
4724 | 0 | newInfo->mIsInterval = aIsInterval; |
4725 | 0 | newInfo->mId = timerId; |
4726 | 0 |
|
4727 | 0 | if (MOZ_UNLIKELY(timerId == INT32_MAX)) { |
4728 | 0 | NS_WARNING("Timeout ids overflowed!"); |
4729 | 0 | mNextTimeoutId = 1; |
4730 | 0 | } |
4731 | 0 |
|
4732 | 0 | newInfo->mHandler = aHandler; |
4733 | 0 |
|
4734 | 0 | // See if any of the optional arguments were passed. |
4735 | 0 | aTimeout = std::max(0, aTimeout); |
4736 | 0 | newInfo->mInterval = TimeDuration::FromMilliseconds(aTimeout); |
4737 | 0 |
|
4738 | 0 | newInfo->mTargetTime = TimeStamp::Now() + newInfo->mInterval; |
4739 | 0 |
|
4740 | 0 | nsAutoPtr<TimeoutInfo>* insertedInfo = |
4741 | 0 | mTimeouts.InsertElementSorted(newInfo.forget(), GetAutoPtrComparator(mTimeouts)); |
4742 | 0 |
|
4743 | 0 | LOG(TimeoutsLog(), ("Worker %p has new timeout: delay=%d interval=%s\n", |
4744 | 0 | this, aTimeout, aIsInterval ? "yes" : "no")); |
4745 | 0 |
|
4746 | 0 | // If the timeout we just made is set to fire next then we need to update the |
4747 | 0 | // timer, unless we're currently running timeouts. |
4748 | 0 | if (insertedInfo == mTimeouts.Elements() && !mRunningExpiredTimeouts) { |
4749 | 0 | if (!mTimer) { |
4750 | 0 | mTimer = NS_NewTimer(); |
4751 | 0 | if (!mTimer) { |
4752 | 0 | aRv.Throw(NS_ERROR_UNEXPECTED); |
4753 | 0 | return 0; |
4754 | 0 | } |
4755 | 0 | |
4756 | 0 | mTimerRunnable = new TimerRunnable(this); |
4757 | 0 | } |
4758 | 0 |
|
4759 | 0 | if (!mTimerRunning) { |
4760 | 0 | if (!ModifyBusyCountFromWorker(true)) { |
4761 | 0 | aRv.Throw(NS_ERROR_FAILURE); |
4762 | 0 | return 0; |
4763 | 0 | } |
4764 | 0 | mTimerRunning = true; |
4765 | 0 | } |
4766 | 0 |
|
4767 | 0 | if (!RescheduleTimeoutTimer(aCx)) { |
4768 | 0 | aRv.Throw(NS_ERROR_FAILURE); |
4769 | 0 | return 0; |
4770 | 0 | } |
4771 | 0 | } |
4772 | 0 | |
4773 | 0 | return timerId; |
4774 | 0 | } |
4775 | | |
4776 | | void |
4777 | | WorkerPrivate::ClearTimeout(int32_t aId) |
4778 | 0 | { |
4779 | 0 | AssertIsOnWorkerThread(); |
4780 | 0 |
|
4781 | 0 | if (!mTimeouts.IsEmpty()) { |
4782 | 0 | NS_ASSERTION(mTimerRunning, "Huh?!"); |
4783 | 0 |
|
4784 | 0 | for (uint32_t index = 0; index < mTimeouts.Length(); index++) { |
4785 | 0 | nsAutoPtr<TimeoutInfo>& info = mTimeouts[index]; |
4786 | 0 | if (info->mId == aId) { |
4787 | 0 | info->mCanceled = true; |
4788 | 0 | break; |
4789 | 0 | } |
4790 | 0 | } |
4791 | 0 | } |
4792 | 0 | } |
4793 | | |
4794 | | bool |
4795 | | WorkerPrivate::RunExpiredTimeouts(JSContext* aCx) |
4796 | 0 | { |
4797 | 0 | AssertIsOnWorkerThread(); |
4798 | 0 |
|
4799 | 0 | // We may be called recursively (e.g. close() inside a timeout) or we could |
4800 | 0 | // have been canceled while this event was pending, bail out if there is |
4801 | 0 | // nothing to do. |
4802 | 0 | if (mRunningExpiredTimeouts || !mTimerRunning) { |
4803 | 0 | return true; |
4804 | 0 | } |
4805 | 0 | |
4806 | 0 | NS_ASSERTION(mTimer && mTimerRunnable, "Must have a timer!"); |
4807 | 0 | NS_ASSERTION(!mTimeouts.IsEmpty(), "Should have some work to do!"); |
4808 | 0 |
|
4809 | 0 | bool retval = true; |
4810 | 0 |
|
4811 | 0 | AutoPtrComparator<TimeoutInfo> comparator = GetAutoPtrComparator(mTimeouts); |
4812 | 0 | JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx)); |
4813 | 0 |
|
4814 | 0 | // We want to make sure to run *something*, even if the timer fired a little |
4815 | 0 | // early. Fudge the value of now to at least include the first timeout. |
4816 | 0 | const TimeStamp actual_now = TimeStamp::Now(); |
4817 | 0 | const TimeStamp now = std::max(actual_now, mTimeouts[0]->mTargetTime); |
4818 | 0 |
|
4819 | 0 | if (now != actual_now) { |
4820 | 0 | LOG(TimeoutsLog(), ("Worker %p fudged timeout by %f ms.\n", this, |
4821 | 0 | (now - actual_now).ToMilliseconds())); |
4822 | 0 | } |
4823 | 0 |
|
4824 | 0 | AutoTArray<TimeoutInfo*, 10> expiredTimeouts; |
4825 | 0 | for (uint32_t index = 0; index < mTimeouts.Length(); index++) { |
4826 | 0 | nsAutoPtr<TimeoutInfo>& info = mTimeouts[index]; |
4827 | 0 | if (info->mTargetTime > now) { |
4828 | 0 | break; |
4829 | 0 | } |
4830 | 0 | expiredTimeouts.AppendElement(info); |
4831 | 0 | } |
4832 | 0 |
|
4833 | 0 | // Guard against recursion. |
4834 | 0 | mRunningExpiredTimeouts = true; |
4835 | 0 |
|
4836 | 0 | // Run expired timeouts. |
4837 | 0 | for (uint32_t index = 0; index < expiredTimeouts.Length(); index++) { |
4838 | 0 | TimeoutInfo*& info = expiredTimeouts[index]; |
4839 | 0 |
|
4840 | 0 | if (info->mCanceled) { |
4841 | 0 | continue; |
4842 | 0 | } |
4843 | 0 | |
4844 | 0 | LOG(TimeoutsLog(), ("Worker %p executing timeout with original delay %f ms.\n", |
4845 | 0 | this, info->mInterval.ToMilliseconds())); |
4846 | 0 |
|
4847 | 0 | // Always check JS_IsExceptionPending if something fails, and if |
4848 | 0 | // JS_IsExceptionPending returns false (i.e. uncatchable exception) then |
4849 | 0 | // break out of the loop. |
4850 | 0 | const char *reason; |
4851 | 0 | if (info->mIsInterval) { |
4852 | 0 | reason = "setInterval handler"; |
4853 | 0 | } else { |
4854 | 0 | reason = "setTimeout handler"; |
4855 | 0 | } |
4856 | 0 |
|
4857 | 0 | RefPtr<Function> callback = info->mHandler->GetCallback(); |
4858 | 0 | if (!callback) { |
4859 | 0 | nsAutoMicroTask mt; |
4860 | 0 |
|
4861 | 0 | AutoEntryScript aes(global, reason, false); |
4862 | 0 |
|
4863 | 0 | // Evaluate the timeout expression. |
4864 | 0 | const nsAString& script = info->mHandler->GetHandlerText(); |
4865 | 0 |
|
4866 | 0 | const char* filename = nullptr; |
4867 | 0 | uint32_t lineNo = 0, dummyColumn = 0; |
4868 | 0 | info->mHandler->GetLocation(&filename, &lineNo, &dummyColumn); |
4869 | 0 |
|
4870 | 0 | JS::CompileOptions options(aes.cx()); |
4871 | 0 | options.setFileAndLine(filename, lineNo).setNoScriptRval(true); |
4872 | 0 |
|
4873 | 0 | JS::Rooted<JS::Value> unused(aes.cx()); |
4874 | 0 |
|
4875 | 0 | JS::SourceBufferHolder srcBuf(script.BeginReading(), script.Length(), |
4876 | 0 | JS::SourceBufferHolder::NoOwnership); |
4877 | 0 | if (!JS::Evaluate(aes.cx(), options, srcBuf, &unused) && |
4878 | 0 | !JS_IsExceptionPending(aCx)) { |
4879 | 0 | retval = false; |
4880 | 0 | break; |
4881 | 0 | } |
4882 | 0 | } else { |
4883 | 0 | ErrorResult rv; |
4884 | 0 | JS::Rooted<JS::Value> ignoredVal(aCx); |
4885 | 0 | callback->Call(GlobalScope(), info->mHandler->GetArgs(), &ignoredVal, rv, |
4886 | 0 | reason); |
4887 | 0 | if (rv.IsUncatchableException()) { |
4888 | 0 | rv.SuppressException(); |
4889 | 0 | retval = false; |
4890 | 0 | break; |
4891 | 0 | } |
4892 | 0 | |
4893 | 0 | rv.SuppressException(); |
4894 | 0 | } |
4895 | 0 |
|
4896 | 0 | NS_ASSERTION(mRunningExpiredTimeouts, "Someone changed this!"); |
4897 | 0 | } |
4898 | 0 |
|
4899 | 0 | // No longer possible to be called recursively. |
4900 | 0 | mRunningExpiredTimeouts = false; |
4901 | 0 |
|
4902 | 0 | // Now remove canceled and expired timeouts from the main list. |
4903 | 0 | // NB: The timeouts present in expiredTimeouts must have the same order |
4904 | 0 | // with respect to each other in mTimeouts. That is, mTimeouts is just |
4905 | 0 | // expiredTimeouts with extra elements inserted. There may be unexpired |
4906 | 0 | // timeouts that have been inserted between the expired timeouts if the |
4907 | 0 | // timeout event handler called setTimeout/setInterval. |
4908 | 0 | for (uint32_t index = 0, expiredTimeoutIndex = 0, |
4909 | 0 | expiredTimeoutLength = expiredTimeouts.Length(); |
4910 | 0 | index < mTimeouts.Length(); ) { |
4911 | 0 | nsAutoPtr<TimeoutInfo>& info = mTimeouts[index]; |
4912 | 0 | if ((expiredTimeoutIndex < expiredTimeoutLength && |
4913 | 0 | info == expiredTimeouts[expiredTimeoutIndex] && |
4914 | 0 | ++expiredTimeoutIndex) || |
4915 | 0 | info->mCanceled) { |
4916 | 0 | if (info->mIsInterval && !info->mCanceled) { |
4917 | 0 | // Reschedule intervals. |
4918 | 0 | info->mTargetTime = info->mTargetTime + info->mInterval; |
4919 | 0 | // Don't resort the list here, we'll do that at the end. |
4920 | 0 | ++index; |
4921 | 0 | } |
4922 | 0 | else { |
4923 | 0 | mTimeouts.RemoveElement(info); |
4924 | 0 | } |
4925 | 0 | } |
4926 | 0 | else { |
4927 | 0 | // If info did not match the current entry in expiredTimeouts, it |
4928 | 0 | // shouldn't be there at all. |
4929 | 0 | NS_ASSERTION(!expiredTimeouts.Contains(info), |
4930 | 0 | "Our timeouts are out of order!"); |
4931 | 0 | ++index; |
4932 | 0 | } |
4933 | 0 | } |
4934 | 0 |
|
4935 | 0 | mTimeouts.Sort(comparator); |
4936 | 0 |
|
4937 | 0 | // Either signal the parent that we're no longer using timeouts or reschedule |
4938 | 0 | // the timer. |
4939 | 0 | if (mTimeouts.IsEmpty()) { |
4940 | 0 | if (!ModifyBusyCountFromWorker(false)) { |
4941 | 0 | retval = false; |
4942 | 0 | } |
4943 | 0 | mTimerRunning = false; |
4944 | 0 | } |
4945 | 0 | else if (retval && !RescheduleTimeoutTimer(aCx)) { |
4946 | 0 | retval = false; |
4947 | 0 | } |
4948 | 0 |
|
4949 | 0 | return retval; |
4950 | 0 | } |
4951 | | |
4952 | | bool |
4953 | | WorkerPrivate::RescheduleTimeoutTimer(JSContext* aCx) |
4954 | 0 | { |
4955 | 0 | AssertIsOnWorkerThread(); |
4956 | 0 | MOZ_ASSERT(!mRunningExpiredTimeouts); |
4957 | 0 | NS_ASSERTION(!mTimeouts.IsEmpty(), "Should have some timeouts!"); |
4958 | 0 | NS_ASSERTION(mTimer && mTimerRunnable, "Should have a timer!"); |
4959 | 0 |
|
4960 | 0 | // NB: This is important! The timer may have already fired, e.g. if a timeout |
4961 | 0 | // callback itself calls setTimeout for a short duration and then takes longer |
4962 | 0 | // than that to finish executing. If that has happened, it's very important |
4963 | 0 | // that we don't execute the event that is now pending in our event queue, or |
4964 | 0 | // our code in RunExpiredTimeouts to "fudge" the timeout value will unleash an |
4965 | 0 | // early timeout when we execute the event we're about to queue. |
4966 | 0 | mTimer->Cancel(); |
4967 | 0 |
|
4968 | 0 | double delta = |
4969 | 0 | (mTimeouts[0]->mTargetTime - TimeStamp::Now()).ToMilliseconds(); |
4970 | 0 | uint32_t delay = delta > 0 ? std::min(delta, double(UINT32_MAX)) : 0; |
4971 | 0 |
|
4972 | 0 | LOG(TimeoutsLog(), ("Worker %p scheduled timer for %d ms, %zu pending timeouts\n", |
4973 | 0 | this, delay, mTimeouts.Length())); |
4974 | 0 |
|
4975 | 0 | nsresult rv = mTimer->InitWithCallback(mTimerRunnable, delay, nsITimer::TYPE_ONE_SHOT); |
4976 | 0 | if (NS_FAILED(rv)) { |
4977 | 0 | JS_ReportErrorASCII(aCx, "Failed to start timer!"); |
4978 | 0 | return false; |
4979 | 0 | } |
4980 | 0 | |
4981 | 0 | return true; |
4982 | 0 | } |
4983 | | |
4984 | | void |
4985 | | WorkerPrivate::StartCancelingTimer() |
4986 | 0 | { |
4987 | 0 | AssertIsOnParentThread(); |
4988 | 0 |
|
4989 | 0 | auto errorCleanup = MakeScopeExit([&] { |
4990 | 0 | mCancelingTimer = nullptr; |
4991 | 0 | }); |
4992 | 0 |
|
4993 | 0 | MOZ_ASSERT(!mCancelingTimer); |
4994 | 0 |
|
4995 | 0 | if (WorkerPrivate* parent = GetParent()) { |
4996 | 0 | mCancelingTimer = NS_NewTimer(parent->ControlEventTarget()); |
4997 | 0 | } else { |
4998 | 0 | mCancelingTimer = NS_NewTimer(); |
4999 | 0 | } |
5000 | 0 |
|
5001 | 0 | if (NS_WARN_IF(!mCancelingTimer)) { |
5002 | 0 | return; |
5003 | 0 | } |
5004 | 0 | |
5005 | 0 | // This is not needed if we are already in an advanced shutdown state. |
5006 | 0 | { |
5007 | 0 | MutexAutoLock lock(mMutex); |
5008 | 0 | if (ParentStatus() >= Canceling) { |
5009 | 0 | return; |
5010 | 0 | } |
5011 | 0 | } |
5012 | 0 | |
5013 | 0 | uint32_t cancelingTimeoutMillis = |
5014 | 0 | StaticPrefs::dom_worker_canceling_timeoutMilliseconds(); |
5015 | 0 |
|
5016 | 0 | RefPtr<CancelingTimerCallback> callback = new CancelingTimerCallback(this); |
5017 | 0 | nsresult rv = mCancelingTimer->InitWithCallback(callback, |
5018 | 0 | cancelingTimeoutMillis, |
5019 | 0 | nsITimer::TYPE_ONE_SHOT); |
5020 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5021 | 0 | return; |
5022 | 0 | } |
5023 | 0 | |
5024 | 0 | errorCleanup.release(); |
5025 | 0 | } |
5026 | | |
5027 | | void |
5028 | | WorkerPrivate::UpdateContextOptionsInternal( |
5029 | | JSContext* aCx, |
5030 | | const JS::ContextOptions& aContextOptions) |
5031 | 0 | { |
5032 | 0 | AssertIsOnWorkerThread(); |
5033 | 0 |
|
5034 | 0 | JS::ContextOptionsRef(aCx) = aContextOptions; |
5035 | 0 |
|
5036 | 0 | for (uint32_t index = 0; index < mChildWorkers.Length(); index++) { |
5037 | 0 | mChildWorkers[index]->UpdateContextOptions(aContextOptions); |
5038 | 0 | } |
5039 | 0 | } |
5040 | | |
5041 | | void |
5042 | | WorkerPrivate::UpdateLanguagesInternal(const nsTArray<nsString>& aLanguages) |
5043 | 0 | { |
5044 | 0 | WorkerGlobalScope* globalScope = GlobalScope(); |
5045 | 0 | if (globalScope) { |
5046 | 0 | RefPtr<WorkerNavigator> nav = globalScope->GetExistingNavigator(); |
5047 | 0 | if (nav) { |
5048 | 0 | nav->SetLanguages(aLanguages); |
5049 | 0 | } |
5050 | 0 | } |
5051 | 0 |
|
5052 | 0 | for (uint32_t index = 0; index < mChildWorkers.Length(); index++) { |
5053 | 0 | mChildWorkers[index]->UpdateLanguages(aLanguages); |
5054 | 0 | } |
5055 | 0 | } |
5056 | | |
5057 | | void |
5058 | | WorkerPrivate::UpdateJSWorkerMemoryParameterInternal(JSContext* aCx, |
5059 | | JSGCParamKey aKey, |
5060 | | uint32_t aValue) |
5061 | 0 | { |
5062 | 0 | AssertIsOnWorkerThread(); |
5063 | 0 |
|
5064 | 0 | // XXX aValue might be 0 here (telling us to unset a previous value for child |
5065 | 0 | // workers). Calling JS_SetGCParameter with a value of 0 isn't actually |
5066 | 0 | // supported though. We really need some way to revert to a default value |
5067 | 0 | // here. |
5068 | 0 | if (aValue) { |
5069 | 0 | JS_SetGCParameter(aCx, aKey, aValue); |
5070 | 0 | } |
5071 | 0 |
|
5072 | 0 | for (uint32_t index = 0; index < mChildWorkers.Length(); index++) { |
5073 | 0 | mChildWorkers[index]->UpdateJSWorkerMemoryParameter(aKey, aValue); |
5074 | 0 | } |
5075 | 0 | } |
5076 | | |
5077 | | #ifdef JS_GC_ZEAL |
5078 | | void |
5079 | | WorkerPrivate::UpdateGCZealInternal(JSContext* aCx, uint8_t aGCZeal, |
5080 | | uint32_t aFrequency) |
5081 | | { |
5082 | | AssertIsOnWorkerThread(); |
5083 | | |
5084 | | JS_SetGCZeal(aCx, aGCZeal, aFrequency); |
5085 | | |
5086 | | for (uint32_t index = 0; index < mChildWorkers.Length(); index++) { |
5087 | | mChildWorkers[index]->UpdateGCZeal(aGCZeal, aFrequency); |
5088 | | } |
5089 | | } |
5090 | | #endif |
5091 | | |
5092 | | void |
5093 | | WorkerPrivate::GarbageCollectInternal(JSContext* aCx, bool aShrinking, |
5094 | | bool aCollectChildren) |
5095 | 0 | { |
5096 | 0 | AssertIsOnWorkerThread(); |
5097 | 0 |
|
5098 | 0 | if (!GlobalScope()) { |
5099 | 0 | // We haven't compiled anything yet. Just bail out. |
5100 | 0 | return; |
5101 | 0 | } |
5102 | 0 | |
5103 | 0 | if (aShrinking || aCollectChildren) { |
5104 | 0 | JS::PrepareForFullGC(aCx); |
5105 | 0 |
|
5106 | 0 | if (aShrinking) { |
5107 | 0 | JS::NonIncrementalGC(aCx, GC_SHRINK, JS::gcreason::DOM_WORKER); |
5108 | 0 |
|
5109 | 0 | if (!aCollectChildren) { |
5110 | 0 | LOG(WorkerLog(), ("Worker %p collected idle garbage\n", this)); |
5111 | 0 | } |
5112 | 0 | } |
5113 | 0 | else { |
5114 | 0 | JS::NonIncrementalGC(aCx, GC_NORMAL, JS::gcreason::DOM_WORKER); |
5115 | 0 | LOG(WorkerLog(), ("Worker %p collected garbage\n", this)); |
5116 | 0 | } |
5117 | 0 | } |
5118 | 0 | else { |
5119 | 0 | JS_MaybeGC(aCx); |
5120 | 0 | LOG(WorkerLog(), ("Worker %p collected periodic garbage\n", this)); |
5121 | 0 | } |
5122 | 0 |
|
5123 | 0 | if (aCollectChildren) { |
5124 | 0 | for (uint32_t index = 0; index < mChildWorkers.Length(); index++) { |
5125 | 0 | mChildWorkers[index]->GarbageCollect(aShrinking); |
5126 | 0 | } |
5127 | 0 | } |
5128 | 0 | } |
5129 | | |
5130 | | void |
5131 | | WorkerPrivate::CycleCollectInternal(bool aCollectChildren) |
5132 | 0 | { |
5133 | 0 | AssertIsOnWorkerThread(); |
5134 | 0 |
|
5135 | 0 | nsCycleCollector_collect(nullptr); |
5136 | 0 |
|
5137 | 0 | if (aCollectChildren) { |
5138 | 0 | for (uint32_t index = 0; index < mChildWorkers.Length(); index++) { |
5139 | 0 | mChildWorkers[index]->CycleCollect(/* dummy = */ false); |
5140 | 0 | } |
5141 | 0 | } |
5142 | 0 | } |
5143 | | |
5144 | | void |
5145 | | WorkerPrivate::MemoryPressureInternal() |
5146 | 0 | { |
5147 | 0 | AssertIsOnWorkerThread(); |
5148 | 0 |
|
5149 | 0 | if (mScope) { |
5150 | 0 | RefPtr<Console> console = mScope->GetConsoleIfExists(); |
5151 | 0 | if (console) { |
5152 | 0 | console->ClearStorage(); |
5153 | 0 | } |
5154 | 0 |
|
5155 | 0 | RefPtr<Performance> performance = mScope->GetPerformanceIfExists(); |
5156 | 0 | if (performance) { |
5157 | 0 | performance->MemoryPressure(); |
5158 | 0 | } |
5159 | 0 | } |
5160 | 0 |
|
5161 | 0 | if (mDebuggerScope) { |
5162 | 0 | RefPtr<Console> console = mDebuggerScope->GetConsoleIfExists(); |
5163 | 0 | if (console) { |
5164 | 0 | console->ClearStorage(); |
5165 | 0 | } |
5166 | 0 | } |
5167 | 0 |
|
5168 | 0 | for (uint32_t index = 0; index < mChildWorkers.Length(); index++) { |
5169 | 0 | mChildWorkers[index]->MemoryPressure(false); |
5170 | 0 | } |
5171 | 0 | } |
5172 | | |
5173 | | void |
5174 | | WorkerPrivate::SetThread(WorkerThread* aThread) |
5175 | 0 | { |
5176 | 0 | if (aThread) { |
5177 | | #ifdef DEBUG |
5178 | | { |
5179 | | bool isOnCurrentThread; |
5180 | | MOZ_ASSERT(NS_SUCCEEDED(aThread->IsOnCurrentThread(&isOnCurrentThread))); |
5181 | | MOZ_ASSERT(isOnCurrentThread); |
5182 | | } |
5183 | | #endif |
5184 | |
|
5185 | 0 | MOZ_ASSERT(!mPRThread); |
5186 | 0 | mPRThread = PRThreadFromThread(aThread); |
5187 | 0 | MOZ_ASSERT(mPRThread); |
5188 | 0 | } |
5189 | 0 | else { |
5190 | 0 | MOZ_ASSERT(mPRThread); |
5191 | 0 | } |
5192 | 0 |
|
5193 | 0 | const WorkerThreadFriendKey friendKey; |
5194 | 0 |
|
5195 | 0 | RefPtr<WorkerThread> doomedThread; |
5196 | 0 |
|
5197 | 0 | { // Scope so that |doomedThread| is released without holding the lock. |
5198 | 0 | MutexAutoLock lock(mMutex); |
5199 | 0 |
|
5200 | 0 | if (aThread) { |
5201 | 0 | MOZ_ASSERT(!mThread); |
5202 | 0 | MOZ_ASSERT(mStatus == Pending); |
5203 | 0 |
|
5204 | 0 | mThread = aThread; |
5205 | 0 | mThread->SetWorker(friendKey, this); |
5206 | 0 |
|
5207 | 0 | if (!mPreStartRunnables.IsEmpty()) { |
5208 | 0 | for (uint32_t index = 0; index < mPreStartRunnables.Length(); index++) { |
5209 | 0 | MOZ_ALWAYS_SUCCEEDS( |
5210 | 0 | mThread->DispatchAnyThread(friendKey, mPreStartRunnables[index].forget())); |
5211 | 0 | } |
5212 | 0 | mPreStartRunnables.Clear(); |
5213 | 0 | } |
5214 | 0 | } |
5215 | 0 | else { |
5216 | 0 | MOZ_ASSERT(mThread); |
5217 | 0 |
|
5218 | 0 | mThread->SetWorker(friendKey, nullptr); |
5219 | 0 |
|
5220 | 0 | mThread.swap(doomedThread); |
5221 | 0 | } |
5222 | 0 | } |
5223 | 0 | } |
5224 | | |
5225 | | void |
5226 | | WorkerPrivate::BeginCTypesCall() |
5227 | 0 | { |
5228 | 0 | AssertIsOnWorkerThread(); |
5229 | 0 |
|
5230 | 0 | // Don't try to GC while we're blocked in a ctypes call. |
5231 | 0 | SetGCTimerMode(NoTimer); |
5232 | 0 | } |
5233 | | |
5234 | | void |
5235 | | WorkerPrivate::EndCTypesCall() |
5236 | 0 | { |
5237 | 0 | AssertIsOnWorkerThread(); |
5238 | 0 |
|
5239 | 0 | // Make sure the periodic timer is running before we start running JS again. |
5240 | 0 | SetGCTimerMode(PeriodicTimer); |
5241 | 0 | } |
5242 | | |
5243 | | bool |
5244 | | WorkerPrivate::ConnectMessagePort(JSContext* aCx, |
5245 | | MessagePortIdentifier& aIdentifier) |
5246 | 0 | { |
5247 | 0 | AssertIsOnWorkerThread(); |
5248 | 0 |
|
5249 | 0 | WorkerGlobalScope* globalScope = GlobalScope(); |
5250 | 0 |
|
5251 | 0 | JS::Rooted<JSObject*> jsGlobal(aCx, globalScope->GetWrapper()); |
5252 | 0 | MOZ_ASSERT(jsGlobal); |
5253 | 0 |
|
5254 | 0 | // This MessagePortIdentifier is used to create a new port, still connected |
5255 | 0 | // with the other one, but in the worker thread. |
5256 | 0 | ErrorResult rv; |
5257 | 0 | RefPtr<MessagePort> port = MessagePort::Create(globalScope, aIdentifier, rv); |
5258 | 0 | if (NS_WARN_IF(rv.Failed())) { |
5259 | 0 | rv.SuppressException(); |
5260 | 0 | return false; |
5261 | 0 | } |
5262 | 0 | |
5263 | 0 | GlobalObject globalObject(aCx, jsGlobal); |
5264 | 0 | if (globalObject.Failed()) { |
5265 | 0 | return false; |
5266 | 0 | } |
5267 | 0 | |
5268 | 0 | RootedDictionary<MessageEventInit> init(aCx); |
5269 | 0 | init.mBubbles = false; |
5270 | 0 | init.mCancelable = false; |
5271 | 0 | init.mSource.SetValue().SetAsMessagePort() = port; |
5272 | 0 | if (!init.mPorts.AppendElement(port.forget(), fallible)) { |
5273 | 0 | return false; |
5274 | 0 | } |
5275 | 0 | |
5276 | 0 | RefPtr<MessageEvent> event = |
5277 | 0 | MessageEvent::Constructor(globalObject, |
5278 | 0 | NS_LITERAL_STRING("connect"), init, rv); |
5279 | 0 |
|
5280 | 0 | event->SetTrusted(true); |
5281 | 0 |
|
5282 | 0 | globalScope->DispatchEvent(*event); |
5283 | 0 |
|
5284 | 0 | return true; |
5285 | 0 | } |
5286 | | |
5287 | | WorkerGlobalScope* |
5288 | | WorkerPrivate::GetOrCreateGlobalScope(JSContext* aCx) |
5289 | 0 | { |
5290 | 0 | AssertIsOnWorkerThread(); |
5291 | 0 |
|
5292 | 0 | if (!mScope) { |
5293 | 0 | RefPtr<WorkerGlobalScope> globalScope; |
5294 | 0 | if (IsSharedWorker()) { |
5295 | 0 | globalScope = new SharedWorkerGlobalScope(this, WorkerName()); |
5296 | 0 | } else if (IsServiceWorker()) { |
5297 | 0 | globalScope = |
5298 | 0 | new ServiceWorkerGlobalScope(this, |
5299 | 0 | GetServiceWorkerRegistrationDescriptor()); |
5300 | 0 | } else { |
5301 | 0 | globalScope = new DedicatedWorkerGlobalScope(this, WorkerName()); |
5302 | 0 | } |
5303 | 0 |
|
5304 | 0 | JS::Rooted<JSObject*> global(aCx); |
5305 | 0 | NS_ENSURE_TRUE(globalScope->WrapGlobalObject(aCx, &global), nullptr); |
5306 | 0 |
|
5307 | 0 | JSAutoRealm ar(aCx, global); |
5308 | 0 |
|
5309 | 0 | // RegisterBindings() can spin a nested event loop so we have to set mScope |
5310 | 0 | // before calling it, and we have to make sure to unset mScope if it fails. |
5311 | 0 | mScope = std::move(globalScope); |
5312 | 0 |
|
5313 | 0 | if (!RegisterBindings(aCx, global)) { |
5314 | 0 | mScope = nullptr; |
5315 | 0 | return nullptr; |
5316 | 0 | } |
5317 | 0 | |
5318 | 0 | JS_FireOnNewGlobalObject(aCx, global); |
5319 | 0 | } |
5320 | 0 |
|
5321 | 0 | return mScope; |
5322 | 0 | } |
5323 | | |
5324 | | WorkerDebuggerGlobalScope* |
5325 | | WorkerPrivate::CreateDebuggerGlobalScope(JSContext* aCx) |
5326 | 0 | { |
5327 | 0 | AssertIsOnWorkerThread(); |
5328 | 0 |
|
5329 | 0 | MOZ_ASSERT(!mDebuggerScope); |
5330 | 0 |
|
5331 | 0 | RefPtr<WorkerDebuggerGlobalScope> globalScope = |
5332 | 0 | new WorkerDebuggerGlobalScope(this); |
5333 | 0 |
|
5334 | 0 | JS::Rooted<JSObject*> global(aCx); |
5335 | 0 | NS_ENSURE_TRUE(globalScope->WrapGlobalObject(aCx, &global), nullptr); |
5336 | 0 |
|
5337 | 0 | JSAutoRealm ar(aCx, global); |
5338 | 0 |
|
5339 | 0 | // RegisterDebuggerBindings() can spin a nested event loop so we have to set |
5340 | 0 | // mDebuggerScope before calling it, and we have to make sure to unset |
5341 | 0 | // mDebuggerScope if it fails. |
5342 | 0 | mDebuggerScope = std::move(globalScope); |
5343 | 0 |
|
5344 | 0 | if (!RegisterDebuggerBindings(aCx, global)) { |
5345 | 0 | mDebuggerScope = nullptr; |
5346 | 0 | return nullptr; |
5347 | 0 | } |
5348 | 0 | |
5349 | 0 | JS_FireOnNewGlobalObject(aCx, global); |
5350 | 0 |
|
5351 | 0 | return mDebuggerScope; |
5352 | 0 | } |
5353 | | |
5354 | | bool |
5355 | | WorkerPrivate::IsOnWorkerThread() const |
5356 | 0 | { |
5357 | 0 | // This is much more complicated than it needs to be but we can't use mThread |
5358 | 0 | // because it must be protected by mMutex and sometimes this method is called |
5359 | 0 | // when mMutex is already locked. This method should always work. |
5360 | 0 | MOZ_ASSERT(mPRThread, |
5361 | 0 | "AssertIsOnWorkerThread() called before a thread was assigned!"); |
5362 | 0 |
|
5363 | 0 | nsCOMPtr<nsIThread> thread; |
5364 | 0 | nsresult rv = |
5365 | 0 | nsThreadManager::get().GetThreadFromPRThread(mPRThread, |
5366 | 0 | getter_AddRefs(thread)); |
5367 | 0 | MOZ_ASSERT(NS_SUCCEEDED(rv)); |
5368 | 0 | MOZ_ASSERT(thread); |
5369 | 0 |
|
5370 | 0 | bool current; |
5371 | 0 | rv = thread->IsOnCurrentThread(¤t); |
5372 | 0 | return NS_SUCCEEDED(rv) && current; |
5373 | 0 | } |
5374 | | |
5375 | | #ifdef DEBUG |
5376 | | void |
5377 | | WorkerPrivate::AssertIsOnWorkerThread() const |
5378 | | { |
5379 | | MOZ_ASSERT(IsOnWorkerThread()); |
5380 | | } |
5381 | | #endif // DEBUG |
5382 | | |
5383 | | void |
5384 | | WorkerPrivate::DumpCrashInformation(nsACString& aString) |
5385 | 0 | { |
5386 | 0 | AssertIsOnWorkerThread(); |
5387 | 0 |
|
5388 | 0 | nsTObserverArray<WorkerHolder*>::ForwardIterator iter(mHolders); |
5389 | 0 | while (iter.HasMore()) { |
5390 | 0 | WorkerHolder* holder = iter.GetNext(); |
5391 | 0 | aString.Append("|"); |
5392 | 0 | aString.Append(holder->Name()); |
5393 | 0 | } |
5394 | 0 | } |
5395 | | |
5396 | | void |
5397 | | WorkerPrivate::EnsurePerformanceCounter() |
5398 | 0 | { |
5399 | 0 | AssertIsOnWorkerThread(); |
5400 | 0 | MOZ_ASSERT(mozilla::StaticPrefs::dom_performance_enable_scheduler_timing()); |
5401 | 0 | if (!mPerformanceCounter) { |
5402 | 0 | nsPrintfCString workerName("Worker:%s", NS_ConvertUTF16toUTF8(mWorkerName).get()); |
5403 | 0 | mPerformanceCounter = new PerformanceCounter(workerName); |
5404 | 0 | } |
5405 | 0 | } |
5406 | | |
5407 | | PerformanceCounter* |
5408 | | WorkerPrivate::GetPerformanceCounter() |
5409 | 0 | { |
5410 | 0 | return mPerformanceCounter; |
5411 | 0 | } |
5412 | | |
5413 | | PerformanceStorage* |
5414 | | WorkerPrivate::GetPerformanceStorage() |
5415 | 0 | { |
5416 | 0 | AssertIsOnMainThread(); |
5417 | 0 | MOZ_ASSERT(mPerformanceStorage); |
5418 | 0 | return mPerformanceStorage; |
5419 | 0 | } |
5420 | | |
5421 | | NS_IMPL_ADDREF(WorkerPrivate::EventTarget) |
5422 | | NS_IMPL_RELEASE(WorkerPrivate::EventTarget) |
5423 | | |
5424 | 0 | NS_INTERFACE_MAP_BEGIN(WorkerPrivate::EventTarget) |
5425 | 0 | NS_INTERFACE_MAP_ENTRY(nsISerialEventTarget) |
5426 | 0 | NS_INTERFACE_MAP_ENTRY(nsIEventTarget) |
5427 | 0 | NS_INTERFACE_MAP_ENTRY(nsISupports) |
5428 | | #ifdef DEBUG |
5429 | | // kDEBUGWorkerEventTargetIID is special in that it does not AddRef its |
5430 | | // result. |
5431 | | if (aIID.Equals(kDEBUGWorkerEventTargetIID)) { |
5432 | | *aInstancePtr = this; |
5433 | | return NS_OK; |
5434 | | } |
5435 | | else |
5436 | | #endif |
5437 | 0 | NS_INTERFACE_MAP_END |
5438 | | |
5439 | | NS_IMETHODIMP |
5440 | | WorkerPrivate::EventTarget::DispatchFromScript(nsIRunnable* aRunnable, |
5441 | | uint32_t aFlags) |
5442 | 0 | { |
5443 | 0 | nsCOMPtr<nsIRunnable> event(aRunnable); |
5444 | 0 | return Dispatch(event.forget(), aFlags); |
5445 | 0 | } |
5446 | | |
5447 | | NS_IMETHODIMP |
5448 | | WorkerPrivate::EventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable, |
5449 | | uint32_t aFlags) |
5450 | 0 | { |
5451 | 0 | // May be called on any thread! |
5452 | 0 | nsCOMPtr<nsIRunnable> event(aRunnable); |
5453 | 0 |
|
5454 | 0 | // Workers only support asynchronous dispatch for now. |
5455 | 0 | if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) { |
5456 | 0 | return NS_ERROR_UNEXPECTED; |
5457 | 0 | } |
5458 | 0 | |
5459 | 0 | RefPtr<WorkerRunnable> workerRunnable; |
5460 | 0 |
|
5461 | 0 | MutexAutoLock lock(mMutex); |
5462 | 0 |
|
5463 | 0 | if (!mWorkerPrivate) { |
5464 | 0 | NS_WARNING("A runnable was posted to a worker that is already shutting " |
5465 | 0 | "down!"); |
5466 | 0 | return NS_ERROR_UNEXPECTED; |
5467 | 0 | } |
5468 | 0 |
|
5469 | 0 | if (event) { |
5470 | 0 | workerRunnable = mWorkerPrivate->MaybeWrapAsWorkerRunnable(event.forget()); |
5471 | 0 | } |
5472 | 0 |
|
5473 | 0 | nsresult rv = |
5474 | 0 | mWorkerPrivate->Dispatch(workerRunnable.forget(), mNestedEventTarget); |
5475 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5476 | 0 | return rv; |
5477 | 0 | } |
5478 | 0 | |
5479 | 0 | return NS_OK; |
5480 | 0 | } |
5481 | | |
5482 | | NS_IMETHODIMP |
5483 | | WorkerPrivate::EventTarget::DelayedDispatch(already_AddRefed<nsIRunnable>, |
5484 | | uint32_t) |
5485 | | |
5486 | 0 | { |
5487 | 0 | return NS_ERROR_NOT_IMPLEMENTED; |
5488 | 0 | } |
5489 | | |
5490 | | NS_IMETHODIMP |
5491 | | WorkerPrivate::EventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread) |
5492 | 0 | { |
5493 | 0 | // May be called on any thread! |
5494 | 0 |
|
5495 | 0 | MOZ_ASSERT(aIsOnCurrentThread); |
5496 | 0 |
|
5497 | 0 | MutexAutoLock lock(mMutex); |
5498 | 0 |
|
5499 | 0 | if (!mWorkerPrivate) { |
5500 | 0 | NS_WARNING("A worker's event target was used after the worker has !"); |
5501 | 0 | return NS_ERROR_UNEXPECTED; |
5502 | 0 | } |
5503 | 0 |
|
5504 | 0 | *aIsOnCurrentThread = mWorkerPrivate->IsOnCurrentThread(); |
5505 | 0 | return NS_OK; |
5506 | 0 | } |
5507 | | |
5508 | | NS_IMETHODIMP_(bool) |
5509 | | WorkerPrivate::EventTarget::IsOnCurrentThreadInfallible() |
5510 | 0 | { |
5511 | 0 | // May be called on any thread! |
5512 | 0 |
|
5513 | 0 | MutexAutoLock lock(mMutex); |
5514 | 0 |
|
5515 | 0 | if (!mWorkerPrivate) { |
5516 | 0 | NS_WARNING("A worker's event target was used after the worker has !"); |
5517 | 0 | return false; |
5518 | 0 | } |
5519 | 0 |
|
5520 | 0 | return mWorkerPrivate->IsOnCurrentThread(); |
5521 | 0 | } |
5522 | | |
5523 | | } // dom namespace |
5524 | | } // mozilla namespace |