1 : /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
2 : /* vim: set ts=2 et sw=2 tw=80: */
3 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is Web Workers.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * The Mozilla Foundation.
20 : * Portions created by the Initial Developer are Copyright (C) 2011
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Ben Turner <bent.mozilla@gmail.com> (Original Author)
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either the GNU General Public License Version 2 or later (the "GPL"), or
28 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "mozilla/Util.h"
41 :
42 : #include "RuntimeService.h"
43 :
44 : #include "nsIDOMChromeWindow.h"
45 : #include "nsIDocument.h"
46 : #include "nsIEffectiveTLDService.h"
47 : #include "nsIObserverService.h"
48 : #include "nsIPlatformCharset.h"
49 : #include "nsIPrincipal.h"
50 : #include "nsIJSContextStack.h"
51 : #include "nsIScriptSecurityManager.h"
52 : #include "nsISupportsPriority.h"
53 : #include "nsITimer.h"
54 : #include "nsPIDOMWindow.h"
55 :
56 : #include "mozilla/Preferences.h"
57 : #include "nsContentUtils.h"
58 : #include "nsDOMJSUtils.h"
59 : #include <Navigator.h>
60 : #include "nsNetUtil.h"
61 : #include "nsServiceManagerUtils.h"
62 : #include "nsThreadUtils.h"
63 : #include "nsXPCOM.h"
64 : #include "nsXPCOMPrivate.h"
65 : #include "xpcpublic.h"
66 :
67 : #include "Events.h"
68 : #include "EventTarget.h"
69 : #include "Worker.h"
70 : #include "WorkerPrivate.h"
71 :
72 : using namespace mozilla;
73 :
74 : USING_WORKERS_NAMESPACE
75 :
76 : using mozilla::MutexAutoLock;
77 : using mozilla::MutexAutoUnlock;
78 : using mozilla::Preferences;
79 : using namespace mozilla::xpconnect::memory;
80 :
81 : // The size of the worker runtime heaps in bytes. May be changed via pref.
82 : #define WORKER_DEFAULT_RUNTIME_HEAPSIZE 32 * 1024 * 1024
83 :
84 : // The C stack size. We use the same stack size on all platforms for
85 : // consistency.
86 : #define WORKER_STACK_SIZE 256 * sizeof(size_t) * 1024
87 :
88 : // The stack limit the JS engine will check.
89 : #ifdef MOZ_ASAN
90 : // For ASan, we need more stack space, so we use all that is available
91 : #define WORKER_CONTEXT_NATIVE_STACK_LIMIT WORKER_STACK_SIZE
92 : #else
93 : // Half the size of the actual C stack, to be safe.
94 : #define WORKER_CONTEXT_NATIVE_STACK_LIMIT 128 * sizeof(size_t) * 1024
95 : #endif
96 :
97 : // The maximum number of threads to use for workers, overridable via pref.
98 : #define MAX_WORKERS_PER_DOMAIN 10
99 :
100 : PR_STATIC_ASSERT(MAX_WORKERS_PER_DOMAIN >= 1);
101 :
102 : // The default number of seconds that close handlers will be allowed to run.
103 : #define MAX_SCRIPT_RUN_TIME_SEC 10
104 :
105 : // The number of seconds that idle threads can hang around before being killed.
106 : #define IDLE_THREAD_TIMEOUT_SEC 30
107 :
108 : // The maximum number of threads that can be idle at one time.
109 : #define MAX_IDLE_THREADS 20
110 :
111 : #define PREF_WORKERS_ENABLED "dom.workers.enabled"
112 : #define PREF_WORKERS_MAX_PER_DOMAIN "dom.workers.maxPerDomain"
113 : #define PREF_WORKERS_GCZEAL "dom.workers.gczeal"
114 : #define PREF_MAX_SCRIPT_RUN_TIME "dom.max_script_run_time"
115 :
116 : #define GC_REQUEST_OBSERVER_TOPIC "child-gc-request"
117 : #define MEMORY_PRESSURE_OBSERVER_TOPIC "memory-pressure"
118 :
119 : #define BROADCAST_ALL_WORKERS(_func, ...) \
120 : PR_BEGIN_MACRO \
121 : AssertIsOnMainThread(); \
122 : \
123 : nsAutoTArray<WorkerPrivate*, 100> workers; \
124 : { \
125 : MutexAutoLock lock(mMutex); \
126 : \
127 : mDomainMap.EnumerateRead(AddAllTopLevelWorkersToArray, &workers); \
128 : } \
129 : \
130 : if (!workers.IsEmpty()) { \
131 : AutoSafeJSContext cx; \
132 : for (PRUint32 index = 0; index < workers.Length(); index++) { \
133 : workers[index]-> _func (cx, ##__VA_ARGS__); \
134 : } \
135 : } \
136 : PR_END_MACRO
137 :
138 : namespace {
139 :
140 : const PRUint32 kNoIndex = PRUint32(-1);
141 :
142 : const PRUint32 kRequiredJSContextOptions =
143 : JSOPTION_DONT_REPORT_UNCAUGHT | JSOPTION_NO_SCRIPT_RVAL;
144 :
145 : PRUint32 gMaxWorkersPerDomain = MAX_WORKERS_PER_DOMAIN;
146 :
147 : // Does not hold an owning reference.
148 : RuntimeService* gRuntimeService = nsnull;
149 :
150 : enum {
151 : ID_Worker = 0,
152 : ID_ChromeWorker,
153 : ID_Event,
154 : ID_MessageEvent,
155 : ID_ErrorEvent,
156 :
157 : ID_COUNT
158 : };
159 :
160 : // These are jsids for the main runtime. Only touched on the main thread.
161 1464 : jsid gStringIDs[ID_COUNT] = { JSID_VOID };
162 :
163 : const char* gStringChars[] = {
164 : "Worker",
165 : "ChromeWorker",
166 : "WorkerEvent",
167 : "WorkerMessageEvent",
168 : "WorkerErrorEvent"
169 :
170 : // XXX Don't care about ProgressEvent since it should never leak to the main
171 : // thread.
172 : };
173 :
174 : PR_STATIC_ASSERT(NS_ARRAY_LENGTH(gStringChars) == ID_COUNT);
175 :
176 : enum {
177 : PREF_strict = 0,
178 : PREF_werror,
179 : PREF_relimit,
180 : PREF_methodjit,
181 : PREF_methodjit_always,
182 : PREF_typeinference,
183 : PREF_jit_hardening,
184 : PREF_mem_max,
185 :
186 : #ifdef JS_GC_ZEAL
187 : PREF_gczeal,
188 : #endif
189 :
190 : PREF_COUNT
191 : };
192 :
193 : #define JS_OPTIONS_DOT_STR "javascript.options."
194 :
195 : const char* gPrefsToWatch[] = {
196 : JS_OPTIONS_DOT_STR "strict",
197 : JS_OPTIONS_DOT_STR "werror",
198 : JS_OPTIONS_DOT_STR "relimit",
199 : JS_OPTIONS_DOT_STR "methodjit.content",
200 : JS_OPTIONS_DOT_STR "methodjit_always",
201 : JS_OPTIONS_DOT_STR "typeinference",
202 : JS_OPTIONS_DOT_STR "jit_hardening",
203 : JS_OPTIONS_DOT_STR "mem.max"
204 :
205 : #ifdef JS_GC_ZEAL
206 : , PREF_WORKERS_GCZEAL
207 : #endif
208 : };
209 :
210 : PR_STATIC_ASSERT(NS_ARRAY_LENGTH(gPrefsToWatch) == PREF_COUNT);
211 :
212 : int
213 0 : PrefCallback(const char* aPrefName, void* aClosure)
214 : {
215 0 : AssertIsOnMainThread();
216 :
217 0 : RuntimeService* rts = static_cast<RuntimeService*>(aClosure);
218 0 : NS_ASSERTION(rts, "This should never be null!");
219 :
220 0 : NS_NAMED_LITERAL_CSTRING(jsOptionStr, JS_OPTIONS_DOT_STR);
221 :
222 0 : if (!strcmp(aPrefName, gPrefsToWatch[PREF_mem_max])) {
223 0 : PRInt32 pref = Preferences::GetInt(aPrefName, -1);
224 : PRUint32 maxBytes = (pref <= 0 || pref >= 0x1000) ?
225 : PRUint32(-1) :
226 0 : PRUint32(pref) * 1024 * 1024;
227 0 : RuntimeService::SetDefaultJSRuntimeHeapSize(maxBytes);
228 0 : rts->UpdateAllWorkerJSRuntimeHeapSize();
229 : }
230 0 : else if (StringBeginsWith(nsDependentCString(aPrefName), jsOptionStr)) {
231 0 : PRUint32 newOptions = kRequiredJSContextOptions;
232 0 : if (Preferences::GetBool(gPrefsToWatch[PREF_strict])) {
233 0 : newOptions |= JSOPTION_STRICT;
234 : }
235 0 : if (Preferences::GetBool(gPrefsToWatch[PREF_werror])) {
236 0 : newOptions |= JSOPTION_WERROR;
237 : }
238 0 : if (Preferences::GetBool(gPrefsToWatch[PREF_relimit])) {
239 0 : newOptions |= JSOPTION_RELIMIT;
240 : }
241 0 : if (Preferences::GetBool(gPrefsToWatch[PREF_methodjit])) {
242 0 : newOptions |= JSOPTION_METHODJIT;
243 : }
244 0 : if (Preferences::GetBool(gPrefsToWatch[PREF_methodjit_always])) {
245 0 : newOptions |= JSOPTION_METHODJIT_ALWAYS;
246 : }
247 0 : if (Preferences::GetBool(gPrefsToWatch[PREF_typeinference])) {
248 0 : newOptions |= JSOPTION_TYPE_INFERENCE;
249 : }
250 :
251 0 : RuntimeService::SetDefaultJSContextOptions(newOptions);
252 0 : rts->UpdateAllWorkerJSContextOptions();
253 : }
254 : #ifdef JS_GC_ZEAL
255 0 : else if (!strcmp(aPrefName, gPrefsToWatch[PREF_gczeal])) {
256 0 : PRInt32 gczeal = Preferences::GetInt(gPrefsToWatch[PREF_gczeal]);
257 0 : RuntimeService::SetDefaultGCZeal(PRUint8(clamped(gczeal, 0, 3)));
258 0 : rts->UpdateAllWorkerGCZeal();
259 : }
260 : #endif
261 0 : return 0;
262 : }
263 :
264 : void
265 0 : ErrorReporter(JSContext* aCx, const char* aMessage, JSErrorReport* aReport)
266 : {
267 0 : WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
268 0 : return worker->ReportError(aCx, aMessage, aReport);
269 : }
270 :
271 : JSBool
272 0 : OperationCallback(JSContext* aCx)
273 : {
274 0 : WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
275 0 : return worker->OperationCallback(aCx);
276 : }
277 :
278 : JSContext*
279 0 : CreateJSContextForWorker(WorkerPrivate* aWorkerPrivate)
280 : {
281 0 : aWorkerPrivate->AssertIsOnWorkerThread();
282 0 : NS_ASSERTION(!aWorkerPrivate->GetJSContext(), "Already has a context!");
283 :
284 : // The number passed here doesn't matter, we're about to change it in the call
285 : // to JS_SetGCParameter.
286 0 : JSRuntime* runtime = JS_NewRuntime(WORKER_DEFAULT_RUNTIME_HEAPSIZE);
287 0 : if (!runtime) {
288 0 : NS_WARNING("Could not create new runtime!");
289 0 : return nsnull;
290 : }
291 :
292 : // This is the real place where we set the max memory for the runtime.
293 : JS_SetGCParameter(runtime, JSGC_MAX_BYTES,
294 0 : aWorkerPrivate->GetJSRuntimeHeapSize());
295 :
296 0 : JS_SetNativeStackQuota(runtime, WORKER_CONTEXT_NATIVE_STACK_LIMIT);
297 :
298 0 : JSContext* workerCx = JS_NewContext(runtime, 0);
299 0 : if (!workerCx) {
300 0 : JS_DestroyRuntime(runtime);
301 0 : NS_WARNING("Could not create new context!");
302 0 : return nsnull;
303 : }
304 :
305 0 : JS_SetContextPrivate(workerCx, aWorkerPrivate);
306 :
307 0 : JS_SetErrorReporter(workerCx, ErrorReporter);
308 :
309 0 : JS_SetOperationCallback(workerCx, OperationCallback);
310 :
311 0 : NS_ASSERTION((aWorkerPrivate->GetJSContextOptions() &
312 : kRequiredJSContextOptions) == kRequiredJSContextOptions,
313 : "Somehow we lost our required options!");
314 0 : JS_SetOptions(workerCx, aWorkerPrivate->GetJSContextOptions());
315 :
316 : #ifdef JS_GC_ZEAL
317 : {
318 0 : PRUint8 zeal = aWorkerPrivate->GetGCZeal();
319 0 : NS_ASSERTION(zeal <= 3, "Bad zeal value!");
320 :
321 0 : PRUint32 frequency = zeal <= 2 ? JS_DEFAULT_ZEAL_FREQ : 1;
322 0 : JS_SetGCZeal(workerCx, zeal, frequency, false);
323 : }
324 : #endif
325 :
326 0 : if (aWorkerPrivate->IsChromeWorker()) {
327 0 : JS_SetVersion(workerCx, JSVERSION_LATEST);
328 : }
329 :
330 0 : return workerCx;
331 : }
332 :
333 : class WorkerThreadRunnable : public nsRunnable
334 0 : {
335 : WorkerPrivate* mWorkerPrivate;
336 :
337 : public:
338 0 : WorkerThreadRunnable(WorkerPrivate* aWorkerPrivate)
339 0 : : mWorkerPrivate(aWorkerPrivate)
340 : {
341 0 : NS_ASSERTION(mWorkerPrivate, "This should never be null!");
342 0 : }
343 :
344 : NS_IMETHOD
345 0 : Run()
346 : {
347 0 : WorkerPrivate* workerPrivate = mWorkerPrivate;
348 0 : mWorkerPrivate = nsnull;
349 :
350 0 : workerPrivate->AssertIsOnWorkerThread();
351 :
352 0 : JSContext* cx = CreateJSContextForWorker(workerPrivate);
353 0 : if (!cx) {
354 : // XXX need to fire an error at parent.
355 0 : NS_ERROR("Failed to create runtime and context!");
356 0 : return NS_ERROR_FAILURE;
357 : }
358 :
359 : {
360 0 : JSAutoRequest ar(cx);
361 0 : workerPrivate->DoRunLoop(cx);
362 : }
363 :
364 0 : JSRuntime* rt = JS_GetRuntime(cx);
365 :
366 : // XXX Bug 666963 - CTypes can create another JSContext for use with
367 : // closures, and then it holds that context in a reserved slot on the CType
368 : // prototype object. We have to destroy that context before we can destroy
369 : // the runtime, and we also have to make sure that it isn't the last context
370 : // to be destroyed (otherwise it will assert). To accomplish this we create
371 : // an unused dummy context, destroy our real context, and then destroy the
372 : // dummy. Once this bug is resolved we can remove this nastiness and simply
373 : // call JS_DestroyContextNoGC on our context.
374 0 : JSContext* dummyCx = JS_NewContext(rt, 0);
375 0 : if (dummyCx) {
376 0 : JS_DestroyContext(cx);
377 0 : JS_DestroyContext(dummyCx);
378 : }
379 : else {
380 0 : NS_WARNING("Failed to create dummy context!");
381 0 : JS_DestroyContext(cx);
382 : }
383 :
384 0 : JS_DestroyRuntime(rt);
385 :
386 0 : workerPrivate->ScheduleDeletion(false);
387 0 : return NS_OK;
388 : }
389 : };
390 :
391 : } /* anonymous namespace */
392 :
393 : BEGIN_WORKERS_NAMESPACE
394 :
395 : // Entry point for the DOM.
396 : JSBool
397 867573 : ResolveWorkerClasses(JSContext* aCx, JSObject* aObj, jsid aId, unsigned aFlags,
398 : JSObject** aObjp)
399 : {
400 867573 : AssertIsOnMainThread();
401 :
402 : // Don't care about assignments or declarations, bail now.
403 867573 : if (aFlags & (JSRESOLVE_ASSIGNING | JSRESOLVE_DECLARING)) {
404 649794 : *aObjp = nsnull;
405 649794 : return true;
406 : }
407 :
408 : // Make sure our strings are interned.
409 217779 : if (JSID_IS_VOID(gStringIDs[0])) {
410 8322 : for (PRUint32 i = 0; i < ID_COUNT; i++) {
411 6935 : JSString* str = JS_InternString(aCx, gStringChars[i]);
412 6935 : if (!str) {
413 0 : while (i) {
414 0 : gStringIDs[--i] = JSID_VOID;
415 : }
416 0 : return false;
417 : }
418 6935 : gStringIDs[i] = INTERNED_STRING_TO_JSID(aCx, str);
419 : }
420 : }
421 :
422 217779 : bool isChrome = false;
423 217779 : bool shouldResolve = false;
424 :
425 1305939 : for (PRUint32 i = 0; i < ID_COUNT; i++) {
426 1088307 : if (aId == gStringIDs[i]) {
427 147 : nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
428 147 : NS_ASSERTION(ssm, "This should never be null!");
429 :
430 : bool enabled;
431 147 : if (NS_FAILED(ssm->IsCapabilityEnabled("UniversalXPConnect", &enabled))) {
432 0 : NS_WARNING("IsCapabilityEnabled failed!");
433 0 : isChrome = false;
434 : }
435 :
436 147 : isChrome = !!enabled;
437 :
438 : // Don't resolve if this is ChromeWorker and we're not chrome. Otherwise
439 : // always resolve.
440 147 : shouldResolve = aId == gStringIDs[ID_ChromeWorker] ? isChrome : true;
441 147 : break;
442 : }
443 : }
444 :
445 217779 : if (shouldResolve) {
446 : // Don't do anything if workers are disabled.
447 147 : if (!isChrome && !Preferences::GetBool(PREF_WORKERS_ENABLED)) {
448 0 : *aObjp = nsnull;
449 0 : return true;
450 : }
451 :
452 147 : JSObject* eventTarget = events::InitEventTargetClass(aCx, aObj, true);
453 147 : if (!eventTarget) {
454 0 : return false;
455 : }
456 :
457 147 : JSObject* worker = worker::InitClass(aCx, aObj, eventTarget, true);
458 147 : if (!worker) {
459 0 : return false;
460 : }
461 :
462 147 : if (isChrome && !chromeworker::InitClass(aCx, aObj, worker, true)) {
463 0 : return false;
464 : }
465 :
466 147 : if (!events::InitClasses(aCx, aObj, true)) {
467 0 : return false;
468 : }
469 :
470 147 : *aObjp = aObj;
471 147 : return true;
472 : }
473 :
474 : // Not resolved.
475 217632 : *aObjp = nsnull;
476 217632 : return true;
477 : }
478 :
479 : void
480 0 : CancelWorkersForWindow(JSContext* aCx, nsPIDOMWindow* aWindow)
481 : {
482 0 : AssertIsOnMainThread();
483 0 : RuntimeService* runtime = RuntimeService::GetService();
484 0 : if (runtime) {
485 0 : runtime->CancelWorkersForWindow(aCx, aWindow);
486 : }
487 0 : }
488 :
489 : void
490 0 : SuspendWorkersForWindow(JSContext* aCx, nsPIDOMWindow* aWindow)
491 : {
492 0 : AssertIsOnMainThread();
493 0 : RuntimeService* runtime = RuntimeService::GetService();
494 0 : if (runtime) {
495 0 : runtime->SuspendWorkersForWindow(aCx, aWindow);
496 : }
497 0 : }
498 :
499 : void
500 0 : ResumeWorkersForWindow(JSContext* aCx, nsPIDOMWindow* aWindow)
501 : {
502 0 : AssertIsOnMainThread();
503 0 : RuntimeService* runtime = RuntimeService::GetService();
504 0 : if (runtime) {
505 0 : runtime->ResumeWorkersForWindow(aCx, aWindow);
506 : }
507 0 : }
508 :
509 : namespace {
510 :
511 : class WorkerTaskRunnable : public WorkerRunnable
512 0 : {
513 : public:
514 0 : WorkerTaskRunnable(WorkerPrivate* aPrivate, WorkerTask* aTask)
515 : : WorkerRunnable(aPrivate, WorkerThread, UnchangedBusyCount,
516 : SkipWhenClearing),
517 0 : mTask(aTask)
518 0 : { }
519 :
520 0 : virtual bool PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) {
521 0 : return true;
522 : }
523 :
524 0 : virtual void PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
525 : bool aDispatchResult)
526 0 : { }
527 :
528 : virtual bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate);
529 :
530 : private:
531 : nsRefPtr<WorkerTask> mTask;
532 : };
533 :
534 : bool
535 0 : WorkerTaskRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
536 : {
537 0 : return mTask->RunTask(aCx);
538 : }
539 :
540 : }
541 :
542 : bool
543 0 : WorkerCrossThreadDispatcher::PostTask(WorkerTask* aTask)
544 : {
545 0 : mozilla::MutexAutoLock lock(mMutex);
546 0 : if (!mPrivate) {
547 0 : return false;
548 : }
549 :
550 0 : nsRefPtr<WorkerTaskRunnable> runnable = new WorkerTaskRunnable(mPrivate, aTask);
551 0 : runnable->Dispatch(nsnull);
552 0 : return true;
553 : }
554 :
555 : END_WORKERS_NAMESPACE
556 :
557 : PRUint32 RuntimeService::sDefaultJSContextOptions = kRequiredJSContextOptions;
558 :
559 : PRUint32 RuntimeService::sDefaultJSRuntimeHeapSize =
560 : WORKER_DEFAULT_RUNTIME_HEAPSIZE;
561 :
562 : PRInt32 RuntimeService::sCloseHandlerTimeoutSeconds = MAX_SCRIPT_RUN_TIME_SEC;
563 :
564 : #ifdef JS_GC_ZEAL
565 : PRUint8 RuntimeService::sDefaultGCZeal = 0;
566 : #endif
567 :
568 0 : RuntimeService::RuntimeService()
569 : : mMutex("RuntimeService::mMutex"), mObserved(false),
570 0 : mShuttingDown(false), mNavigatorStringsLoaded(false)
571 : {
572 0 : AssertIsOnMainThread();
573 0 : NS_ASSERTION(!gRuntimeService, "More than one service!");
574 0 : }
575 :
576 0 : RuntimeService::~RuntimeService()
577 : {
578 0 : AssertIsOnMainThread();
579 :
580 : // gRuntimeService can be null if Init() fails.
581 0 : NS_ASSERTION(!gRuntimeService || gRuntimeService == this,
582 : "More than one service!");
583 :
584 0 : gRuntimeService = nsnull;
585 0 : }
586 :
587 : // static
588 : RuntimeService*
589 0 : RuntimeService::GetOrCreateService()
590 : {
591 0 : AssertIsOnMainThread();
592 :
593 0 : if (!gRuntimeService) {
594 0 : nsRefPtr<RuntimeService> service = new RuntimeService();
595 0 : if (NS_FAILED(service->Init())) {
596 0 : NS_WARNING("Failed to initialize!");
597 0 : service->Cleanup();
598 0 : return nsnull;
599 : }
600 :
601 : // The observer service now owns us until shutdown.
602 0 : gRuntimeService = service;
603 : }
604 :
605 0 : return gRuntimeService;
606 : }
607 :
608 : // static
609 : RuntimeService*
610 0 : RuntimeService::GetService()
611 : {
612 0 : return gRuntimeService;
613 : }
614 :
615 : bool
616 0 : RuntimeService::RegisterWorker(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
617 : {
618 0 : aWorkerPrivate->AssertIsOnParentThread();
619 :
620 0 : WorkerPrivate* parent = aWorkerPrivate->GetParent();
621 0 : if (!parent) {
622 0 : AssertIsOnMainThread();
623 :
624 0 : if (mShuttingDown) {
625 0 : JS_ReportError(aCx, "Cannot create worker during shutdown!");
626 0 : return false;
627 : }
628 : }
629 :
630 : WorkerDomainInfo* domainInfo;
631 0 : bool queued = false;
632 : {
633 0 : const nsCString& domain = aWorkerPrivate->Domain();
634 :
635 0 : MutexAutoLock lock(mMutex);
636 :
637 0 : if (!mDomainMap.Get(domain, &domainInfo)) {
638 0 : NS_ASSERTION(!parent, "Shouldn't have a parent here!");
639 :
640 0 : domainInfo = new WorkerDomainInfo();
641 0 : domainInfo->mDomain = domain;
642 :
643 0 : if (!mDomainMap.Put(domain, domainInfo)) {
644 0 : delete domainInfo;
645 0 : domainInfo = nsnull;
646 : }
647 : }
648 :
649 0 : if (domainInfo) {
650 : queued = gMaxWorkersPerDomain &&
651 0 : domainInfo->ActiveWorkerCount() >= gMaxWorkersPerDomain &&
652 0 : !domain.IsEmpty();
653 :
654 0 : if (queued) {
655 0 : domainInfo->mQueuedWorkers.AppendElement(aWorkerPrivate);
656 : }
657 0 : else if (parent) {
658 0 : domainInfo->mChildWorkerCount++;
659 : }
660 : else {
661 0 : domainInfo->mActiveWorkers.AppendElement(aWorkerPrivate);
662 : }
663 : }
664 : }
665 :
666 0 : if (!domainInfo) {
667 0 : JS_ReportOutOfMemory(aCx);
668 0 : return false;
669 : }
670 :
671 : // From here on out we must call UnregisterWorker if something fails!
672 0 : if (parent) {
673 0 : if (!parent->AddChildWorker(aCx, aWorkerPrivate)) {
674 0 : UnregisterWorker(aCx, aWorkerPrivate);
675 0 : return false;
676 : }
677 : }
678 : else {
679 0 : if (!mNavigatorStringsLoaded) {
680 0 : if (NS_FAILED(NS_GetNavigatorAppName(mNavigatorStrings.mAppName)) ||
681 0 : NS_FAILED(NS_GetNavigatorAppVersion(mNavigatorStrings.mAppVersion)) ||
682 0 : NS_FAILED(NS_GetNavigatorPlatform(mNavigatorStrings.mPlatform)) ||
683 0 : NS_FAILED(NS_GetNavigatorUserAgent(mNavigatorStrings.mUserAgent))) {
684 0 : JS_ReportError(aCx, "Failed to load navigator strings!");
685 0 : UnregisterWorker(aCx, aWorkerPrivate);
686 0 : return false;
687 : }
688 :
689 0 : mNavigatorStringsLoaded = true;
690 : }
691 :
692 0 : nsPIDOMWindow* window = aWorkerPrivate->GetWindow();
693 :
694 : nsTArray<WorkerPrivate*>* windowArray;
695 0 : if (!mWindowMap.Get(window, &windowArray)) {
696 0 : NS_ASSERTION(!parent, "Shouldn't have a parent here!");
697 :
698 0 : windowArray = new nsTArray<WorkerPrivate*>(1);
699 :
700 0 : if (!mWindowMap.Put(window, windowArray)) {
701 0 : delete windowArray;
702 0 : UnregisterWorker(aCx, aWorkerPrivate);
703 0 : JS_ReportOutOfMemory(aCx);
704 0 : return false;
705 : }
706 : }
707 :
708 0 : NS_ASSERTION(!windowArray->Contains(aWorkerPrivate),
709 : "Already know about this worker!");
710 0 : windowArray->AppendElement(aWorkerPrivate);
711 : }
712 :
713 0 : if (!queued && !ScheduleWorker(aCx, aWorkerPrivate)) {
714 0 : return false;
715 : }
716 :
717 0 : return true;
718 : }
719 :
720 : void
721 0 : RuntimeService::UnregisterWorker(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
722 : {
723 0 : aWorkerPrivate->AssertIsOnParentThread();
724 :
725 0 : WorkerPrivate* parent = aWorkerPrivate->GetParent();
726 0 : if (!parent) {
727 0 : AssertIsOnMainThread();
728 : }
729 :
730 0 : WorkerPrivate* queuedWorker = nsnull;
731 : {
732 0 : const nsCString& domain = aWorkerPrivate->Domain();
733 :
734 0 : MutexAutoLock lock(mMutex);
735 :
736 : WorkerDomainInfo* domainInfo;
737 0 : if (!mDomainMap.Get(domain, &domainInfo)) {
738 0 : NS_ERROR("Don't have an entry for this domain!");
739 : }
740 :
741 : // Remove old worker from everywhere.
742 0 : PRUint32 index = domainInfo->mQueuedWorkers.IndexOf(aWorkerPrivate);
743 0 : if (index != kNoIndex) {
744 : // Was queued, remove from the list.
745 0 : domainInfo->mQueuedWorkers.RemoveElementAt(index);
746 : }
747 0 : else if (parent) {
748 0 : NS_ASSERTION(domainInfo->mChildWorkerCount, "Must be non-zero!");
749 0 : domainInfo->mChildWorkerCount--;
750 : }
751 : else {
752 0 : NS_ASSERTION(domainInfo->mActiveWorkers.Contains(aWorkerPrivate),
753 : "Don't know about this worker!");
754 0 : domainInfo->mActiveWorkers.RemoveElement(aWorkerPrivate);
755 : }
756 :
757 : // See if there's a queued worker we can schedule.
758 0 : if (domainInfo->ActiveWorkerCount() < gMaxWorkersPerDomain &&
759 0 : !domainInfo->mQueuedWorkers.IsEmpty()) {
760 0 : queuedWorker = domainInfo->mQueuedWorkers[0];
761 0 : domainInfo->mQueuedWorkers.RemoveElementAt(0);
762 :
763 0 : if (queuedWorker->GetParent()) {
764 0 : domainInfo->mChildWorkerCount++;
765 : }
766 : else {
767 0 : domainInfo->mActiveWorkers.AppendElement(queuedWorker);
768 : }
769 : }
770 :
771 0 : if (!domainInfo->ActiveWorkerCount()) {
772 0 : NS_ASSERTION(domainInfo->mQueuedWorkers.IsEmpty(), "Huh?!");
773 0 : mDomainMap.Remove(domain);
774 : }
775 : }
776 :
777 0 : if (parent) {
778 0 : parent->RemoveChildWorker(aCx, aWorkerPrivate);
779 : }
780 : else {
781 0 : nsPIDOMWindow* window = aWorkerPrivate->GetWindow();
782 :
783 : nsTArray<WorkerPrivate*>* windowArray;
784 0 : if (!mWindowMap.Get(window, &windowArray)) {
785 0 : NS_ERROR("Don't have an entry for this window!");
786 : }
787 :
788 0 : NS_ASSERTION(windowArray->Contains(aWorkerPrivate),
789 : "Don't know about this worker!");
790 0 : windowArray->RemoveElement(aWorkerPrivate);
791 :
792 0 : if (windowArray->IsEmpty()) {
793 0 : NS_ASSERTION(!queuedWorker, "How can this be?!");
794 0 : mWindowMap.Remove(window);
795 : }
796 : }
797 :
798 0 : if (queuedWorker && !ScheduleWorker(aCx, queuedWorker)) {
799 0 : UnregisterWorker(aCx, queuedWorker);
800 : }
801 0 : }
802 :
803 : bool
804 0 : RuntimeService::ScheduleWorker(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
805 : {
806 0 : if (!aWorkerPrivate->Start()) {
807 : // This is ok, means that we didn't need to make a thread for this worker.
808 0 : return true;
809 : }
810 :
811 0 : nsCOMPtr<nsIThread> thread;
812 : {
813 0 : MutexAutoLock lock(mMutex);
814 0 : if (!mIdleThreadArray.IsEmpty()) {
815 0 : PRUint32 index = mIdleThreadArray.Length() - 1;
816 0 : mIdleThreadArray[index].mThread.swap(thread);
817 0 : mIdleThreadArray.RemoveElementAt(index);
818 : }
819 : }
820 :
821 0 : if (!thread) {
822 0 : if (NS_FAILED(NS_NewThread(getter_AddRefs(thread), nsnull,
823 : WORKER_STACK_SIZE))) {
824 0 : UnregisterWorker(aCx, aWorkerPrivate);
825 0 : JS_ReportError(aCx, "Could not create new thread!");
826 0 : return false;
827 : }
828 :
829 0 : nsCOMPtr<nsISupportsPriority> priority = do_QueryInterface(thread);
830 0 : if (!priority ||
831 0 : NS_FAILED(priority->SetPriority(nsISupportsPriority::PRIORITY_LOW))) {
832 0 : NS_WARNING("Could not lower the new thread's priority!");
833 : }
834 : }
835 :
836 : #ifdef DEBUG
837 0 : aWorkerPrivate->SetThread(thread);
838 : #endif
839 :
840 0 : nsCOMPtr<nsIRunnable> runnable = new WorkerThreadRunnable(aWorkerPrivate);
841 0 : if (NS_FAILED(thread->Dispatch(runnable, NS_DISPATCH_NORMAL))) {
842 0 : UnregisterWorker(aCx, aWorkerPrivate);
843 0 : JS_ReportError(aCx, "Could not dispatch to thread!");
844 0 : return false;
845 : }
846 :
847 0 : return true;
848 : }
849 :
850 : // static
851 : void
852 0 : RuntimeService::ShutdownIdleThreads(nsITimer* aTimer, void* /* aClosure */)
853 : {
854 0 : AssertIsOnMainThread();
855 :
856 0 : RuntimeService* runtime = RuntimeService::GetService();
857 0 : NS_ASSERTION(runtime, "This should never be null!");
858 :
859 0 : NS_ASSERTION(aTimer == runtime->mIdleThreadTimer, "Wrong timer!");
860 :
861 : // Cheat a little and grab all threads that expire within one second of now.
862 0 : TimeStamp now = TimeStamp::Now() + TimeDuration::FromSeconds(1);
863 :
864 0 : TimeStamp nextExpiration;
865 :
866 0 : nsAutoTArray<nsCOMPtr<nsIThread>, 20> expiredThreads;
867 : {
868 0 : MutexAutoLock lock(runtime->mMutex);
869 :
870 0 : for (PRUint32 index = 0; index < runtime->mIdleThreadArray.Length();
871 : index++) {
872 0 : IdleThreadInfo& info = runtime->mIdleThreadArray[index];
873 0 : if (info.mExpirationTime > now) {
874 0 : nextExpiration = info.mExpirationTime;
875 0 : break;
876 : }
877 :
878 0 : nsCOMPtr<nsIThread>* thread = expiredThreads.AppendElement();
879 0 : thread->swap(info.mThread);
880 : }
881 :
882 0 : if (!expiredThreads.IsEmpty()) {
883 0 : runtime->mIdleThreadArray.RemoveElementsAt(0, expiredThreads.Length());
884 : }
885 : }
886 :
887 0 : NS_ASSERTION(nextExpiration.IsNull() || !expiredThreads.IsEmpty(),
888 : "Should have a new time or there should be some threads to shut "
889 : "down");
890 :
891 0 : for (PRUint32 index = 0; index < expiredThreads.Length(); index++) {
892 0 : if (NS_FAILED(expiredThreads[index]->Shutdown())) {
893 0 : NS_WARNING("Failed to shutdown thread!");
894 : }
895 : }
896 :
897 0 : if (!nextExpiration.IsNull()) {
898 0 : TimeDuration delta = nextExpiration - TimeStamp::Now();
899 0 : PRUint32 delay(delta > TimeDuration(0) ? delta.ToMilliseconds() : 0);
900 :
901 : // Reschedule the timer.
902 0 : if (NS_FAILED(aTimer->InitWithFuncCallback(ShutdownIdleThreads, nsnull,
903 : delay,
904 : nsITimer::TYPE_ONE_SHOT))) {
905 0 : NS_ERROR("Can't schedule timer!");
906 : }
907 : }
908 0 : }
909 :
910 : nsresult
911 0 : RuntimeService::Init()
912 : {
913 0 : AssertIsOnMainThread();
914 :
915 0 : mIdleThreadTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
916 0 : NS_ENSURE_STATE(mIdleThreadTimer);
917 :
918 0 : bool ok = mDomainMap.Init();
919 0 : NS_ENSURE_STATE(ok);
920 :
921 0 : ok = mWindowMap.Init();
922 0 : NS_ENSURE_STATE(ok);
923 :
924 0 : nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
925 0 : NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
926 :
927 : nsresult rv =
928 0 : obs->AddObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false);
929 0 : NS_ENSURE_SUCCESS(rv, rv);
930 :
931 0 : mObserved = true;
932 :
933 0 : if (NS_FAILED(obs->AddObserver(this, GC_REQUEST_OBSERVER_TOPIC, false))) {
934 0 : NS_WARNING("Failed to register for GC request notifications!");
935 : }
936 :
937 0 : if (NS_FAILED(obs->AddObserver(this, MEMORY_PRESSURE_OBSERVER_TOPIC,
938 : false))) {
939 0 : NS_WARNING("Failed to register for memory pressure notifications!");
940 : }
941 :
942 0 : for (PRUint32 index = 0; index < ArrayLength(gPrefsToWatch); index++) {
943 0 : if (NS_FAILED(Preferences::RegisterCallback(PrefCallback,
944 : gPrefsToWatch[index], this))) {
945 0 : NS_WARNING("Failed to register pref callback?!");
946 : }
947 0 : PrefCallback(gPrefsToWatch[index], this);
948 : }
949 :
950 : // We assume atomic 32bit reads/writes. If this assumption doesn't hold on
951 : // some wacky platform then the worst that could happen is that the close
952 : // handler will run for a slightly different amount of time.
953 0 : if (NS_FAILED(Preferences::AddIntVarCache(&sCloseHandlerTimeoutSeconds,
954 : PREF_MAX_SCRIPT_RUN_TIME,
955 : MAX_SCRIPT_RUN_TIME_SEC))) {
956 0 : NS_WARNING("Failed to register timeout cache?!");
957 : }
958 :
959 : PRInt32 maxPerDomain = Preferences::GetInt(PREF_WORKERS_MAX_PER_DOMAIN,
960 0 : MAX_WORKERS_PER_DOMAIN);
961 0 : gMaxWorkersPerDomain = NS_MAX(0, maxPerDomain);
962 :
963 0 : mDetectorName = Preferences::GetLocalizedCString("intl.charset.detector");
964 :
965 : nsCOMPtr<nsIPlatformCharset> platformCharset =
966 0 : do_GetService(NS_PLATFORMCHARSET_CONTRACTID, &rv);
967 0 : if (NS_SUCCEEDED(rv)) {
968 0 : rv = platformCharset->GetCharset(kPlatformCharsetSel_PlainTextInFile,
969 0 : mSystemCharset);
970 : }
971 :
972 0 : return NS_OK;
973 : }
974 :
975 : // This spins the event loop until all workers are finished and their threads
976 : // have been joined.
977 : void
978 0 : RuntimeService::Cleanup()
979 : {
980 0 : AssertIsOnMainThread();
981 :
982 0 : nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
983 0 : NS_WARN_IF_FALSE(obs, "Failed to get observer service?!");
984 :
985 : // Tell anyone that cares that they're about to lose worker support.
986 0 : if (obs && NS_FAILED(obs->NotifyObservers(nsnull, WORKERS_SHUTDOWN_TOPIC,
987 : nsnull))) {
988 0 : NS_WARNING("NotifyObservers failed!");
989 : }
990 :
991 : // That's it, no more workers.
992 0 : mShuttingDown = true;
993 :
994 0 : if (mIdleThreadTimer) {
995 0 : if (NS_FAILED(mIdleThreadTimer->Cancel())) {
996 0 : NS_WARNING("Failed to cancel idle timer!");
997 : }
998 0 : mIdleThreadTimer = nsnull;
999 : }
1000 :
1001 0 : if (mDomainMap.IsInitialized()) {
1002 0 : MutexAutoLock lock(mMutex);
1003 :
1004 0 : nsAutoTArray<WorkerPrivate*, 100> workers;
1005 0 : mDomainMap.EnumerateRead(AddAllTopLevelWorkersToArray, &workers);
1006 :
1007 0 : if (!workers.IsEmpty()) {
1008 : nsIThread* currentThread;
1009 :
1010 : // Cancel all top-level workers.
1011 : {
1012 0 : MutexAutoUnlock unlock(mMutex);
1013 :
1014 0 : currentThread = NS_GetCurrentThread();
1015 0 : NS_ASSERTION(currentThread, "This should never be null!");
1016 :
1017 0 : AutoSafeJSContext cx;
1018 :
1019 0 : for (PRUint32 index = 0; index < workers.Length(); index++) {
1020 0 : if (!workers[index]->Kill(cx)) {
1021 0 : NS_WARNING("Failed to cancel worker!");
1022 : }
1023 : }
1024 : }
1025 :
1026 : // Shut down any idle threads.
1027 0 : if (!mIdleThreadArray.IsEmpty()) {
1028 0 : nsAutoTArray<nsCOMPtr<nsIThread>, 20> idleThreads;
1029 :
1030 0 : PRUint32 idleThreadCount = mIdleThreadArray.Length();
1031 0 : idleThreads.SetLength(idleThreadCount);
1032 :
1033 0 : for (PRUint32 index = 0; index < idleThreadCount; index++) {
1034 0 : NS_ASSERTION(mIdleThreadArray[index].mThread, "Null thread!");
1035 0 : idleThreads[index].swap(mIdleThreadArray[index].mThread);
1036 : }
1037 :
1038 0 : mIdleThreadArray.Clear();
1039 :
1040 0 : MutexAutoUnlock unlock(mMutex);
1041 :
1042 0 : for (PRUint32 index = 0; index < idleThreadCount; index++) {
1043 0 : if (NS_FAILED(idleThreads[index]->Shutdown())) {
1044 0 : NS_WARNING("Failed to shutdown thread!");
1045 : }
1046 : }
1047 : }
1048 :
1049 : // And make sure all their final messages have run and all their threads
1050 : // have joined.
1051 0 : while (mDomainMap.Count()) {
1052 0 : MutexAutoUnlock unlock(mMutex);
1053 :
1054 0 : if (!NS_ProcessNextEvent(currentThread)) {
1055 0 : NS_WARNING("Something bad happened!");
1056 : break;
1057 : }
1058 : }
1059 : }
1060 : }
1061 :
1062 0 : if (mWindowMap.IsInitialized()) {
1063 0 : NS_ASSERTION(!mWindowMap.Count(), "All windows should have been released!");
1064 : }
1065 :
1066 0 : if (mObserved) {
1067 0 : for (PRUint32 index = 0; index < ArrayLength(gPrefsToWatch); index++) {
1068 0 : Preferences::UnregisterCallback(PrefCallback, gPrefsToWatch[index], this);
1069 : }
1070 :
1071 0 : if (obs) {
1072 0 : if (NS_FAILED(obs->RemoveObserver(this, GC_REQUEST_OBSERVER_TOPIC))) {
1073 0 : NS_WARNING("Failed to unregister for GC request notifications!");
1074 : }
1075 :
1076 0 : if (NS_FAILED(obs->RemoveObserver(this,
1077 : MEMORY_PRESSURE_OBSERVER_TOPIC))) {
1078 0 : NS_WARNING("Failed to unregister for memory pressure notifications!");
1079 : }
1080 :
1081 : nsresult rv =
1082 0 : obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID);
1083 0 : mObserved = NS_FAILED(rv);
1084 : }
1085 : }
1086 0 : }
1087 :
1088 : // static
1089 : PLDHashOperator
1090 0 : RuntimeService::AddAllTopLevelWorkersToArray(const nsACString& aKey,
1091 : WorkerDomainInfo* aData,
1092 : void* aUserArg)
1093 : {
1094 : nsTArray<WorkerPrivate*>* array =
1095 0 : static_cast<nsTArray<WorkerPrivate*>*>(aUserArg);
1096 :
1097 : #ifdef DEBUG
1098 0 : for (PRUint32 index = 0; index < aData->mActiveWorkers.Length(); index++) {
1099 0 : NS_ASSERTION(!aData->mActiveWorkers[index]->GetParent(),
1100 : "Shouldn't have a parent in this list!");
1101 : }
1102 : #endif
1103 :
1104 0 : array->AppendElements(aData->mActiveWorkers);
1105 :
1106 : // These might not be top-level workers...
1107 0 : for (PRUint32 index = 0; index < aData->mQueuedWorkers.Length(); index++) {
1108 0 : WorkerPrivate* worker = aData->mQueuedWorkers[index];
1109 0 : if (!worker->GetParent()) {
1110 0 : array->AppendElement(worker);
1111 : }
1112 : }
1113 :
1114 0 : return PL_DHASH_NEXT;
1115 : }
1116 :
1117 : void
1118 0 : RuntimeService::GetWorkersForWindow(nsPIDOMWindow* aWindow,
1119 : nsTArray<WorkerPrivate*>& aWorkers)
1120 : {
1121 0 : AssertIsOnMainThread();
1122 :
1123 : nsTArray<WorkerPrivate*>* workers;
1124 0 : if (mWindowMap.Get(aWindow, &workers)) {
1125 0 : NS_ASSERTION(!workers->IsEmpty(), "Should have been removed!");
1126 0 : aWorkers.AppendElements(*workers);
1127 : }
1128 : else {
1129 0 : NS_ASSERTION(aWorkers.IsEmpty(), "Should be empty!");
1130 : }
1131 0 : }
1132 :
1133 : void
1134 0 : RuntimeService::CancelWorkersForWindow(JSContext* aCx,
1135 : nsPIDOMWindow* aWindow)
1136 : {
1137 0 : AssertIsOnMainThread();
1138 :
1139 0 : nsAutoTArray<WorkerPrivate*, 100> workers;
1140 0 : GetWorkersForWindow(aWindow, workers);
1141 :
1142 0 : if (!workers.IsEmpty()) {
1143 0 : AutoSafeJSContext cx(aCx);
1144 0 : for (PRUint32 index = 0; index < workers.Length(); index++) {
1145 0 : if (!workers[index]->Cancel(aCx)) {
1146 0 : NS_WARNING("Failed to cancel worker!");
1147 : }
1148 : }
1149 : }
1150 0 : }
1151 :
1152 : void
1153 0 : RuntimeService::SuspendWorkersForWindow(JSContext* aCx,
1154 : nsPIDOMWindow* aWindow)
1155 : {
1156 0 : AssertIsOnMainThread();
1157 :
1158 0 : nsAutoTArray<WorkerPrivate*, 100> workers;
1159 0 : GetWorkersForWindow(aWindow, workers);
1160 :
1161 0 : if (!workers.IsEmpty()) {
1162 0 : AutoSafeJSContext cx(aCx);
1163 0 : for (PRUint32 index = 0; index < workers.Length(); index++) {
1164 0 : if (!workers[index]->Suspend(aCx)) {
1165 0 : NS_WARNING("Failed to cancel worker!");
1166 : }
1167 : }
1168 : }
1169 0 : }
1170 :
1171 : void
1172 0 : RuntimeService::ResumeWorkersForWindow(JSContext* aCx,
1173 : nsPIDOMWindow* aWindow)
1174 : {
1175 0 : AssertIsOnMainThread();
1176 :
1177 0 : nsAutoTArray<WorkerPrivate*, 100> workers;
1178 0 : GetWorkersForWindow(aWindow, workers);
1179 :
1180 0 : if (!workers.IsEmpty()) {
1181 0 : AutoSafeJSContext cx(aCx);
1182 0 : for (PRUint32 index = 0; index < workers.Length(); index++) {
1183 0 : if (!workers[index]->Resume(aCx)) {
1184 0 : NS_WARNING("Failed to cancel worker!");
1185 : }
1186 : }
1187 : }
1188 0 : }
1189 :
1190 : void
1191 0 : RuntimeService::NoteIdleThread(nsIThread* aThread)
1192 : {
1193 0 : AssertIsOnMainThread();
1194 0 : NS_ASSERTION(aThread, "Null pointer!");
1195 :
1196 : static TimeDuration timeout =
1197 0 : TimeDuration::FromSeconds(IDLE_THREAD_TIMEOUT_SEC);
1198 :
1199 0 : TimeStamp expirationTime = TimeStamp::Now() + timeout;
1200 :
1201 : bool shutdown;
1202 0 : if (mShuttingDown) {
1203 0 : shutdown = true;
1204 : }
1205 : else {
1206 0 : MutexAutoLock lock(mMutex);
1207 :
1208 0 : if (mIdleThreadArray.Length() < MAX_IDLE_THREADS) {
1209 0 : IdleThreadInfo* info = mIdleThreadArray.AppendElement();
1210 0 : info->mThread = aThread;
1211 0 : info->mExpirationTime = expirationTime;
1212 0 : shutdown = false;
1213 : }
1214 : else {
1215 0 : shutdown = true;
1216 : }
1217 : }
1218 :
1219 : // Too many idle threads, just shut this one down.
1220 0 : if (shutdown) {
1221 0 : if (NS_FAILED(aThread->Shutdown())) {
1222 0 : NS_WARNING("Failed to shutdown thread!");
1223 : }
1224 0 : return;
1225 : }
1226 :
1227 : // Schedule timer.
1228 0 : if (NS_FAILED(mIdleThreadTimer->
1229 : InitWithFuncCallback(ShutdownIdleThreads, nsnull,
1230 : IDLE_THREAD_TIMEOUT_SEC * 1000,
1231 : nsITimer::TYPE_ONE_SHOT))) {
1232 0 : NS_ERROR("Can't schedule timer!");
1233 : }
1234 : }
1235 :
1236 : void
1237 0 : RuntimeService::UpdateAllWorkerJSContextOptions()
1238 : {
1239 0 : BROADCAST_ALL_WORKERS(UpdateJSContextOptions, GetDefaultJSContextOptions());
1240 0 : }
1241 :
1242 : void
1243 0 : RuntimeService::UpdateAllWorkerJSRuntimeHeapSize()
1244 : {
1245 0 : BROADCAST_ALL_WORKERS(UpdateJSRuntimeHeapSize, GetDefaultJSRuntimeHeapSize());
1246 0 : }
1247 :
1248 : #ifdef JS_GC_ZEAL
1249 : void
1250 0 : RuntimeService::UpdateAllWorkerGCZeal()
1251 : {
1252 0 : BROADCAST_ALL_WORKERS(UpdateGCZeal, GetDefaultGCZeal());
1253 0 : }
1254 : #endif
1255 :
1256 : void
1257 0 : RuntimeService::GarbageCollectAllWorkers(bool aShrinking)
1258 : {
1259 0 : BROADCAST_ALL_WORKERS(GarbageCollect, aShrinking);
1260 0 : }
1261 :
1262 : // nsISupports
1263 0 : NS_IMPL_ISUPPORTS1(RuntimeService, nsIObserver)
1264 :
1265 : // nsIObserver
1266 : NS_IMETHODIMP
1267 0 : RuntimeService::Observe(nsISupports* aSubject, const char* aTopic,
1268 : const PRUnichar* aData)
1269 : {
1270 0 : AssertIsOnMainThread();
1271 :
1272 0 : if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID)) {
1273 0 : Cleanup();
1274 0 : return NS_OK;
1275 : }
1276 0 : if (!strcmp(aTopic, GC_REQUEST_OBSERVER_TOPIC)) {
1277 0 : GarbageCollectAllWorkers(false);
1278 0 : return NS_OK;
1279 : }
1280 0 : if (!strcmp(aTopic, MEMORY_PRESSURE_OBSERVER_TOPIC)) {
1281 0 : GarbageCollectAllWorkers(true);
1282 0 : return NS_OK;
1283 : }
1284 :
1285 0 : NS_NOTREACHED("Unknown observer topic!");
1286 0 : return NS_OK;
1287 : }
1288 :
1289 0 : RuntimeService::AutoSafeJSContext::AutoSafeJSContext(JSContext* aCx)
1290 0 : : mContext(aCx ? aCx : GetSafeContext())
1291 : {
1292 0 : AssertIsOnMainThread();
1293 :
1294 0 : if (mContext) {
1295 0 : nsIThreadJSContextStack* stack = nsContentUtils::ThreadJSContextStack();
1296 0 : NS_ASSERTION(stack, "This should never be null!");
1297 :
1298 0 : if (NS_FAILED(stack->Push(mContext))) {
1299 0 : NS_ERROR("Couldn't push safe JSContext!");
1300 0 : mContext = nsnull;
1301 0 : return;
1302 : }
1303 :
1304 0 : JS_BeginRequest(mContext);
1305 : }
1306 : }
1307 :
1308 0 : RuntimeService::AutoSafeJSContext::~AutoSafeJSContext()
1309 : {
1310 0 : AssertIsOnMainThread();
1311 :
1312 0 : if (mContext) {
1313 0 : JS_ReportPendingException(mContext);
1314 :
1315 0 : JS_EndRequest(mContext);
1316 :
1317 0 : nsIThreadJSContextStack* stack = nsContentUtils::ThreadJSContextStack();
1318 0 : NS_ASSERTION(stack, "This should never be null!");
1319 :
1320 : JSContext* cx;
1321 0 : if (NS_FAILED(stack->Pop(&cx))) {
1322 0 : NS_ERROR("Failed to pop safe context!");
1323 : }
1324 0 : if (cx != mContext) {
1325 0 : NS_ERROR("Mismatched context!");
1326 : }
1327 : }
1328 0 : }
1329 :
1330 : // static
1331 : JSContext*
1332 0 : RuntimeService::AutoSafeJSContext::GetSafeContext()
1333 : {
1334 0 : AssertIsOnMainThread();
1335 :
1336 0 : nsIThreadJSContextStack* stack = nsContentUtils::ThreadJSContextStack();
1337 0 : NS_ASSERTION(stack, "This should never be null!");
1338 :
1339 : JSContext* cx;
1340 0 : if (NS_FAILED(stack->GetSafeJSContext(&cx))) {
1341 0 : NS_ERROR("Couldn't get safe JSContext!");
1342 0 : return nsnull;
1343 : }
1344 :
1345 0 : NS_ASSERTION(!JS_IsExceptionPending(cx), "Already has an exception?!");
1346 0 : return cx;
1347 4392 : }
|