Bug 462525 - username truncation code is unnecessarily duplicated in nsLoginManagerP...
[wine-gecko.git] / dom / src / threads / nsDOMThreadService.cpp
blob58f091211f47cac054418dac8ccdca2fd9d03df0
1 /* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is worker threads.
17 * The Initial Developer of the Original Code is
18 * Mozilla Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 2008
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Vladimir Vukicevic <vladimir@pobox.com> (Original Author)
24 * Ben Turner <bent.mozilla@gmail.com>
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.
38 * ***** END LICENSE BLOCK ***** */
40 #include "nsDOMThreadService.h"
42 // Interfaces
43 #include "nsIComponentManager.h"
44 #include "nsIConsoleService.h"
45 #include "nsIDocument.h"
46 #include "nsIDOMDocument.h"
47 #include "nsIEventTarget.h"
48 #include "nsIGenericFactory.h"
49 #include "nsIJSContextStack.h"
50 #include "nsIJSRuntimeService.h"
51 #include "nsIObserverService.h"
52 #include "nsIScriptError.h"
53 #include "nsIScriptGlobalObject.h"
54 #include "nsIServiceManager.h"
55 #include "nsISupportsPriority.h"
56 #include "nsIThreadPool.h"
57 #include "nsIXPConnect.h"
59 // Other includes
60 #include "nsAutoLock.h"
61 #include "nsAutoPtr.h"
62 #include "nsContentUtils.h"
63 #include "nsDeque.h"
64 #include "nsIClassInfoImpl.h"
65 #include "nsProxyRelease.h"
66 #include "nsThreadUtils.h"
67 #include "nsXPCOM.h"
68 #include "nsXPCOMCID.h"
69 #include "nsXPCOMCIDInternal.h"
70 #include "pratom.h"
71 #include "prthread.h"
73 // DOMWorker includes
74 #include "nsDOMWorkerPool.h"
75 #include "nsDOMWorkerSecurityManager.h"
76 #include "nsDOMWorkerTimeout.h"
78 #ifdef PR_LOGGING
79 PRLogModuleInfo *gDOMThreadsLog = nsnull;
80 #endif
81 #define LOG(_args) PR_LOG(gDOMThreadsLog, PR_LOG_DEBUG, _args)
83 // The maximum number of threads in the internal thread pool
84 #define THREADPOOL_MAX_THREADS 3
86 PR_STATIC_ASSERT(THREADPOOL_MAX_THREADS >= 1);
88 // The maximum number of idle threads in the internal thread pool
89 #define THREADPOOL_IDLE_THREADS 3
91 PR_STATIC_ASSERT(THREADPOOL_MAX_THREADS >= THREADPOOL_IDLE_THREADS);
93 // As we suspend threads for various reasons (navigating away from the page,
94 // loading scripts, etc.) we open another slot in the thread pool for another
95 // worker to use. We can't do this forever so we set an absolute cap on the
96 // number of threads we'll allow to prevent DOS attacks.
97 #define THREADPOOL_THREAD_CAP 20
99 PR_STATIC_ASSERT(THREADPOOL_THREAD_CAP >= THREADPOOL_MAX_THREADS);
101 // The number of times our JS operation callback will be called before yielding
102 // the thread
103 #define CALLBACK_YIELD_THRESHOLD 100
105 // A "bad" value for the NSPR TLS functions.
106 #define BAD_TLS_INDEX (PRUintn)-1
108 // Don't know why nsISupports.idl defines this out...
109 #define NS_FORWARD_NSISUPPORTS(_to) \
110 NS_IMETHOD QueryInterface(const nsIID& uuid, void** result) { \
111 return _to QueryInterface(uuid, result); \
113 NS_IMETHOD_(nsrefcnt) AddRef(void) { return _to AddRef(); } \
114 NS_IMETHOD_(nsrefcnt) Release(void) { return _to Release(); }
116 // Easy access for static functions. No reference here.
117 static nsDOMThreadService* gDOMThreadService = nsnull;
119 // These pointers actually carry references and must be released.
120 static nsIObserverService* gObserverService = nsnull;
121 static nsIJSRuntimeService* gJSRuntimeService = nsnull;
122 static nsIThreadJSContextStack* gThreadJSContextStack = nsnull;
123 static nsIXPCSecurityManager* gWorkerSecurityManager = nsnull;
125 PRUintn gJSContextIndex = BAD_TLS_INDEX;
128 * Simple class to automatically destroy a JSContext to make error handling
129 * easier.
131 class JSAutoContextDestroyer
133 public:
134 JSAutoContextDestroyer(JSContext* aCx)
135 : mCx(aCx) { }
137 ~JSAutoContextDestroyer() {
138 if (mCx) {
139 nsContentUtils::XPConnect()->ReleaseJSContext(mCx, PR_TRUE);
143 operator JSContext*() {
144 return mCx;
147 JSContext* forget() {
148 JSContext* cx = mCx;
149 mCx = nsnull;
150 return cx;
153 private:
154 JSContext* mCx;
158 * This class is used as to post an error to the main thread. It logs the error
159 * to the console and calls the pool's onError callback.
161 class nsReportErrorRunnable : public nsRunnable
163 public:
164 nsReportErrorRunnable(nsIScriptError* aError, nsDOMWorkerThread* aWorker)
165 : mError(aError), mWorker(aWorker) { }
167 NS_IMETHOD Run() {
168 nsresult rv;
170 nsCOMPtr<nsIConsoleService> consoleService =
171 do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv);
172 if (NS_SUCCEEDED(rv)) {
173 consoleService->LogMessage(mError);
176 if (!mWorker->IsCanceled()) {
177 #ifdef PR_LOGGING
178 nsAutoString message;
179 mError->GetErrorMessage(message);
180 #endif
181 nsRefPtr<nsDOMWorkerPool> pool = mWorker->Pool();
183 LOG(("Posting error '%s' to pool [0x%p]",
184 NS_LossyConvertUTF16toASCII(message).get(),
185 static_cast<void*>(pool.get())));
187 pool->HandleError(mError, mWorker);
189 return NS_OK;
192 private:
193 // XXX Maybe this should be an nsIException...
194 nsCOMPtr<nsIScriptError> mError;
196 // Have to carry a strong ref since this is used as a parameter to the
197 // onError callback.
198 nsRefPtr<nsDOMWorkerThread> mWorker;
202 * Need this to expose an nsIScriptError to content JS (implement nsIClassInfo
203 * with DOM_OBJECT flag set.
205 class nsDOMWorkerScriptError : public nsIClassInfo
207 public:
208 NS_DECL_ISUPPORTS
209 NS_DECL_NSICLASSINFO
211 nsDOMWorkerScriptError(nsIScriptError* aError)
212 : mScriptError(this, aError) { }
214 protected:
216 // Lame, nsIScriptError and nsIClassInfo both have 'readonly attribute
217 // unsigned long flags' so we have to use an inner class to do this the
218 // right way...
219 class InnerScriptError : public nsIScriptError
221 public:
222 NS_FORWARD_NSISUPPORTS(mParent->)
223 NS_FORWARD_NSISCRIPTERROR(mError->)
224 NS_FORWARD_NSICONSOLEMESSAGE(mError->)
226 InnerScriptError(nsDOMWorkerScriptError* aParent, nsIScriptError* aError)
227 : mParent(aParent), mError(aError) { }
229 protected:
230 nsDOMWorkerScriptError* mParent;
231 nsCOMPtr<nsIScriptError> mError;
234 InnerScriptError mScriptError;
237 NS_IMPL_THREADSAFE_ADDREF(nsDOMWorkerScriptError)
238 NS_IMPL_THREADSAFE_RELEASE(nsDOMWorkerScriptError)
240 // More hoops to jump through for the identical IDL methods
241 NS_INTERFACE_MAP_BEGIN(nsDOMWorkerScriptError)
242 if (aIID.Equals(NS_GET_IID(nsIScriptError)) ||
243 aIID.Equals(NS_GET_IID(nsIConsoleMessage))) {
244 foundInterface = static_cast<nsIConsoleMessage*>(&mScriptError);
246 else
247 NS_INTERFACE_MAP_ENTRY(nsIClassInfo)
248 NS_INTERFACE_MAP_ENTRY(nsISupports)
249 NS_INTERFACE_MAP_END
251 NS_IMPL_CI_INTERFACE_GETTER2(nsDOMWorkerScriptError, nsIScriptError,
252 nsIConsoleMessage)
254 NS_IMPL_THREADSAFE_DOM_CI(nsDOMWorkerScriptError)
257 * Used to post an expired timeout to the correct worker.
259 class nsDOMWorkerTimeoutRunnable : public nsRunnable
261 public:
262 nsDOMWorkerTimeoutRunnable(nsDOMWorkerTimeout* aTimeout)
263 : mTimeout(aTimeout) { }
265 NS_IMETHOD Run() {
266 return mTimeout->Run();
268 protected:
269 nsRefPtr<nsDOMWorkerTimeout> mTimeout;
273 * This class exists to solve a particular problem: Calling Dispatch on a
274 * thread pool will always create a new thread to service the runnable as long
275 * as the thread limit has not been reached. Since our DOM workers can only be
276 * accessed by one thread at a time we could end up spawning a new thread that
277 * does nothing but wait initially. There is no way to control this behavior
278 * currently so we cheat by using a runnable that emulates a thread. The
279 * nsDOMThreadService's monitor protects the queue of events.
281 class nsDOMWorkerRunnable : public nsRunnable
283 friend class nsDOMThreadService;
285 public:
286 nsDOMWorkerRunnable(nsDOMWorkerThread* aWorker)
287 : mWorker(aWorker) { }
289 virtual ~nsDOMWorkerRunnable() {
290 nsCOMPtr<nsIRunnable> runnable;
291 while ((runnable = dont_AddRef((nsIRunnable*)mRunnables.PopFront()))) {
292 // Loop until all the runnables are dead.
295 // Only release mWorker on the main thread!
296 nsDOMWorkerThread* worker = nsnull;
297 mWorker.swap(worker);
299 nsISupports* supports = NS_ISUPPORTS_CAST(nsIDOMWorkerThread*, worker);
300 NS_ASSERTION(supports, "This should never be null!");
302 nsCOMPtr<nsIThread> mainThread(do_GetMainThread());
303 NS_ProxyRelease(mainThread, supports);
306 void PutRunnable(nsIRunnable* aRunnable) {
307 NS_ASSERTION(aRunnable, "Null pointer!");
309 NS_ADDREF(aRunnable);
311 // No need to enter the monitor because we should already be in it.
313 mRunnables.Push(aRunnable);
316 NS_IMETHOD Run() {
317 // This must have been set up by the thread service
318 NS_ASSERTION(gJSContextIndex != BAD_TLS_INDEX, "No context index!");
320 // Make sure we have a JSContext to run everything on.
321 JSContext* cx = (JSContext*)PR_GetThreadPrivate(gJSContextIndex);
322 NS_ASSERTION(cx, "nsDOMThreadService didn't give us a context!");
324 JS_SetContextPrivate(cx, mWorker);
326 // Tell the worker which context it will be using
327 if (mWorker->SetGlobalForContext(cx)) {
328 RunQueue();
330 // Remove the global object from the context so that it might be garbage
331 // collected.
332 JS_SetGlobalObject(cx, NULL);
333 JS_SetContextPrivate(cx, NULL);
335 else {
336 // This is usually due to a parse error in the worker script...
337 JS_SetGlobalObject(cx, NULL);
339 nsAutoMonitor mon(gDOMThreadService->mMonitor);
340 gDOMThreadService->WorkerComplete(this);
341 mon.NotifyAll();
344 return NS_OK;
347 protected:
349 void RunQueue() {
350 JSContext* cx = (JSContext*)PR_GetThreadPrivate(gJSContextIndex);
351 NS_ASSERTION(cx, "nsDOMThreadService didn't give us a context!");
353 while (1) {
354 nsCOMPtr<nsIRunnable> runnable;
356 nsAutoMonitor mon(gDOMThreadService->mMonitor);
358 runnable = dont_AddRef((nsIRunnable*)mRunnables.PopFront());
360 if (!runnable || mWorker->IsCanceled()) {
361 #ifdef PR_LOGGING
362 if (mWorker->IsCanceled()) {
363 LOG(("Bailing out of run loop for canceled worker[0x%p]",
364 static_cast<void*>(mWorker.get())));
366 #endif
367 gDOMThreadService->WorkerComplete(this);
368 mon.NotifyAll();
369 return;
373 // Clear out any old cruft hanging around in the regexp statics.
374 JS_ClearRegExpStatics(cx);
376 #ifdef DEBUG
377 nsresult rv =
378 #endif
379 runnable->Run();
380 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Runnable failed!");
384 // Set at construction
385 nsRefPtr<nsDOMWorkerThread> mWorker;
387 // Protected by mMonitor
388 nsDeque mRunnables;
391 /*******************************************************************************
392 * JS environment function and callbacks
395 JSBool
396 DOMWorkerOperationCallback(JSContext* aCx)
398 nsDOMWorkerThread* worker = (nsDOMWorkerThread*)JS_GetContextPrivate(aCx);
400 // Want a strong ref here to make sure that the monitor we wait on won't go
401 // away.
402 nsRefPtr<nsDOMWorkerPool> pool;
404 PRBool wasSuspended = PR_FALSE;
405 PRBool extraThreadAllowed = PR_FALSE;
406 jsrefcount suspendDepth = 0;
408 while (1) {
409 // Kill execution if we're canceled.
410 if (worker->IsCanceled()) {
411 LOG(("Forcefully killing JS for worker [0x%p]",
412 static_cast<void*>(worker)));
414 if (wasSuspended) {
415 if (extraThreadAllowed) {
416 gDOMThreadService->ChangeThreadPoolMaxThreads(-1);
418 JS_ResumeRequest(aCx, suspendDepth);
421 // Kill exectuion of the currently running JS.
422 return PR_FALSE;
425 // Break out if we're not suspended.
426 if (!worker->IsSuspended()) {
427 if (wasSuspended) {
428 if (extraThreadAllowed) {
429 gDOMThreadService->ChangeThreadPoolMaxThreads(-1);
431 JS_ResumeRequest(aCx, suspendDepth);
433 break;
436 if (!wasSuspended) {
437 // Make sure we can get the monitor we need to wait on. It's possible that
438 // the worker was canceled since we checked above.
439 if (worker->IsCanceled()) {
440 NS_WARNING("Tried to suspend on a pool that has gone away");
441 return PR_FALSE;
444 pool = worker->Pool();
446 // Make sure to suspend our request while we block like this, otherwise we
447 // prevent GC for everyone.
448 suspendDepth = JS_SuspendRequest(aCx);
450 // Since we're going to block this thread we should open up a new thread
451 // in the thread pool for other workers. Must check the return value to
452 // make sure we don't decrement when we failed.
453 extraThreadAllowed =
454 NS_SUCCEEDED(gDOMThreadService->ChangeThreadPoolMaxThreads(1));
456 // Only do all this setup once.
457 wasSuspended = PR_TRUE;
460 nsAutoMonitor mon(pool->Monitor());
461 mon.Wait();
464 // Since only one thread can access a context at once we don't have to worry
465 // about atomically incrementing this counter
466 if (++worker->mCallbackCount >= CALLBACK_YIELD_THRESHOLD) {
467 // Must call this so that GC can happen on the main thread!
468 JS_YieldRequest(aCx);
470 // Start the counter over.
471 worker->mCallbackCount = 0;
474 // Continue execution.
475 return JS_TRUE;
478 void
479 DOMWorkerErrorReporter(JSContext* aCx,
480 const char* aMessage,
481 JSErrorReport* aReport)
483 NS_ASSERTION(!NS_IsMainThread(), "Huh?!");
485 nsDOMWorkerThread* worker = (nsDOMWorkerThread*)JS_GetContextPrivate(aCx);
487 if (worker->IsCanceled()) {
488 // We don't want to report errors from canceled workers. It's very likely
489 // that we only returned an error in the first place because the worker was
490 // already canceled.
491 return;
494 nsresult rv;
495 nsCOMPtr<nsIScriptError> errorObject =
496 do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
497 NS_ENSURE_SUCCESS(rv,);
499 const PRUnichar* message =
500 reinterpret_cast<const PRUnichar*>(aReport->ucmessage);
502 nsAutoString filename;
503 filename.AssignWithConversion(aReport->filename);
505 const PRUnichar* line =
506 reinterpret_cast<const PRUnichar*>(aReport->uclinebuf);
508 PRUint32 column = aReport->uctokenptr - aReport->uclinebuf;
510 rv = errorObject->Init(message, filename.get(), line, aReport->lineno,
511 column, aReport->flags, "DOM Worker javascript");
512 NS_ENSURE_SUCCESS(rv,);
514 nsRefPtr<nsDOMWorkerScriptError> domError =
515 new nsDOMWorkerScriptError(errorObject);
516 NS_ENSURE_TRUE(domError,);
518 nsCOMPtr<nsIScriptError> scriptError(do_QueryInterface(domError));
519 NS_ENSURE_TRUE(scriptError,);
521 nsCOMPtr<nsIThread> mainThread(do_GetMainThread());
522 NS_ENSURE_TRUE(mainThread,);
524 nsCOMPtr<nsIRunnable> runnable =
525 new nsReportErrorRunnable(scriptError, worker);
526 NS_ENSURE_TRUE(runnable,);
528 rv = mainThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
529 NS_ENSURE_SUCCESS(rv,);
532 /*******************************************************************************
533 * nsDOMThreadService
536 nsDOMThreadService::nsDOMThreadService()
537 : mMonitor(nsnull)
539 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
540 #ifdef PR_LOGGING
541 if (!gDOMThreadsLog) {
542 gDOMThreadsLog = PR_NewLogModule("nsDOMThreads");
544 #endif
545 LOG(("Initializing DOM Thread service"));
548 nsDOMThreadService::~nsDOMThreadService()
550 LOG(("DOM Thread service destroyed"));
552 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
554 Cleanup();
556 if (mMonitor) {
557 nsAutoMonitor::DestroyMonitor(mMonitor);
561 NS_IMPL_THREADSAFE_ISUPPORTS4(nsDOMThreadService, nsIEventTarget,
562 nsIObserver,
563 nsIThreadPoolListener,
564 nsIDOMThreadService)
566 nsresult
567 nsDOMThreadService::Init()
569 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
570 NS_ASSERTION(!gDOMThreadService, "Only one instance should ever be created!");
572 nsresult rv;
573 nsCOMPtr<nsIObserverService> obs =
574 do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv);
575 NS_ENSURE_SUCCESS(rv, rv);
577 rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_FALSE);
578 NS_ENSURE_SUCCESS(rv, rv);
580 obs.forget(&gObserverService);
582 mThreadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID, &rv);
583 NS_ENSURE_SUCCESS(rv, rv);
585 rv = mThreadPool->SetListener(this);
586 NS_ENSURE_SUCCESS(rv, rv);
588 rv = mThreadPool->SetThreadLimit(THREADPOOL_MAX_THREADS);
589 NS_ENSURE_SUCCESS(rv, rv);
591 rv = mThreadPool->SetIdleThreadLimit(THREADPOOL_IDLE_THREADS);
592 NS_ENSURE_SUCCESS(rv, rv);
594 mMonitor = nsAutoMonitor::NewMonitor("nsDOMThreadService::mMonitor");
595 NS_ENSURE_TRUE(mMonitor, NS_ERROR_OUT_OF_MEMORY);
597 PRBool success = mWorkersInProgress.Init();
598 NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
600 nsCOMPtr<nsIJSRuntimeService>
601 runtimeSvc(do_GetService("@mozilla.org/js/xpc/RuntimeService;1"));
602 NS_ENSURE_TRUE(runtimeSvc, NS_ERROR_FAILURE);
603 runtimeSvc.forget(&gJSRuntimeService);
605 nsCOMPtr<nsIThreadJSContextStack>
606 contextStack(do_GetService("@mozilla.org/js/xpc/ContextStack;1"));
607 NS_ENSURE_TRUE(contextStack, NS_ERROR_FAILURE);
608 contextStack.forget(&gThreadJSContextStack);
610 nsCOMPtr<nsIXPCSecurityManager> secMan(new nsDOMWorkerSecurityManager());
611 NS_ENSURE_TRUE(secMan, NS_ERROR_OUT_OF_MEMORY);
612 secMan.forget(&gWorkerSecurityManager);
614 if (gJSContextIndex == BAD_TLS_INDEX &&
615 PR_NewThreadPrivateIndex(&gJSContextIndex, NULL) != PR_SUCCESS) {
616 NS_ERROR("PR_NewThreadPrivateIndex failed!");
617 gJSContextIndex = BAD_TLS_INDEX;
618 return NS_ERROR_FAILURE;
621 return NS_OK;
624 /* static */
625 already_AddRefed<nsIDOMThreadService>
626 nsDOMThreadService::GetOrInitService()
628 if (!gDOMThreadService) {
629 nsRefPtr<nsDOMThreadService> service = new nsDOMThreadService();
630 NS_ENSURE_TRUE(service, nsnull);
632 nsresult rv = service->Init();
633 NS_ENSURE_SUCCESS(rv, nsnull);
635 service.swap(gDOMThreadService);
638 nsCOMPtr<nsIDOMThreadService> service(gDOMThreadService);
639 return service.forget();
642 /* static */
643 nsDOMThreadService*
644 nsDOMThreadService::get()
646 return gDOMThreadService;
649 /* static */
650 void
651 nsDOMThreadService::Shutdown()
653 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
654 NS_IF_RELEASE(gDOMThreadService);
657 void
658 nsDOMThreadService::Cleanup()
660 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
662 // This will either be called at 'xpcom-shutdown' or earlier if the call to
663 // Init fails somehow. We can therefore assume that all services will still
664 // be available here.
666 if (gObserverService) {
667 gObserverService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
668 NS_RELEASE(gObserverService);
671 // The thread pool holds a circular reference to this service through its
672 // listener. We must shut down the thread pool manually to break this cycle.
673 if (mThreadPool) {
674 mThreadPool->Shutdown();
675 mThreadPool = nsnull;
678 // Need to force a GC so that all of our workers get cleaned up.
679 if (gThreadJSContextStack) {
680 JSContext* safeContext;
681 if (NS_SUCCEEDED(gThreadJSContextStack->GetSafeJSContext(&safeContext))) {
682 JS_GC(safeContext);
684 NS_RELEASE(gThreadJSContextStack);
687 // These must be released after the thread pool is shut down.
688 NS_IF_RELEASE(gJSRuntimeService);
689 NS_IF_RELEASE(gWorkerSecurityManager);
692 nsresult
693 nsDOMThreadService::Dispatch(nsDOMWorkerThread* aWorker,
694 nsIRunnable* aRunnable)
696 NS_ASSERTION(aWorker, "Null pointer!");
697 NS_ASSERTION(aRunnable, "Null pointer!");
699 NS_ASSERTION(mThreadPool, "Dispatch called after 'xpcom-shutdown'!");
701 if (aWorker->IsCanceled()) {
702 LOG(("Will not dispatch runnable [0x%p] for canceled worker [0x%p]",
703 static_cast<void*>(aRunnable), static_cast<void*>(aWorker)));
704 return NS_ERROR_NOT_AVAILABLE;
707 nsRefPtr<nsDOMWorkerRunnable> workerRunnable;
709 nsAutoMonitor mon(mMonitor);
711 if (mWorkersInProgress.Get(aWorker, getter_AddRefs(workerRunnable))) {
712 workerRunnable->PutRunnable(aRunnable);
713 return NS_OK;
716 workerRunnable = new nsDOMWorkerRunnable(aWorker);
717 NS_ENSURE_TRUE(workerRunnable, NS_ERROR_OUT_OF_MEMORY);
719 workerRunnable->PutRunnable(aRunnable);
721 PRBool success = mWorkersInProgress.Put(aWorker, workerRunnable);
722 NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
725 nsresult rv = mThreadPool->Dispatch(workerRunnable, NS_DISPATCH_NORMAL);
727 // XXX This is a mess and it could probably be removed once we have an
728 // infallible malloc implementation.
729 if (NS_FAILED(rv)) {
730 NS_WARNING("Failed to dispatch runnable to thread pool!");
732 nsAutoMonitor mon(mMonitor);
734 // We exited the monitor after inserting the runnable into the table so make
735 // sure we're removing the right one!
736 nsRefPtr<nsDOMWorkerRunnable> tableRunnable;
737 if (mWorkersInProgress.Get(aWorker, getter_AddRefs(tableRunnable)) &&
738 workerRunnable == tableRunnable) {
739 mWorkersInProgress.Remove(aWorker);
741 // And don't forget to tell anyone who's waiting.
742 mon.NotifyAll();
745 return rv;
748 return NS_OK;
751 void
752 nsDOMThreadService::WorkerComplete(nsDOMWorkerRunnable* aRunnable)
755 // No need to be in the monitor here because we should already be in it.
757 #ifdef DEBUG
758 nsRefPtr<nsDOMWorkerThread>& debugWorker = aRunnable->mWorker;
760 nsRefPtr<nsDOMWorkerRunnable> runnable;
761 NS_ASSERTION(mWorkersInProgress.Get(debugWorker, getter_AddRefs(runnable)) &&
762 runnable == aRunnable,
763 "Removing a worker that isn't in our hashtable?!");
764 #endif
766 mWorkersInProgress.Remove(aRunnable->mWorker);
769 void
770 nsDOMThreadService::WaitForCanceledWorker(nsDOMWorkerThread* aWorker)
772 NS_ASSERTION(aWorker->IsCanceled(),
773 "Waiting on a worker that isn't canceled!");
775 nsAutoMonitor mon(mMonitor);
777 while (mWorkersInProgress.Get(aWorker, nsnull)) {
778 mon.Wait();
782 /* static */
783 JSContext*
784 nsDOMThreadService::CreateJSContext()
786 JSRuntime* rt;
787 gJSRuntimeService->GetRuntime(&rt);
788 NS_ENSURE_TRUE(rt, nsnull);
790 JSAutoContextDestroyer cx(JS_NewContext(rt, 8192));
791 NS_ENSURE_TRUE(cx, nsnull);
793 JS_SetErrorReporter(cx, DOMWorkerErrorReporter);
795 JS_SetOperationCallback(cx, DOMWorkerOperationCallback,
796 100 * JS_OPERATION_WEIGHT_BASE);
798 static JSSecurityCallbacks securityCallbacks = {
799 nsDOMWorkerSecurityManager::JSCheckAccess,
800 nsDOMWorkerSecurityManager::JSTranscodePrincipals,
801 nsDOMWorkerSecurityManager::JSFindPrincipal
804 JS_SetContextSecurityCallbacks(cx, &securityCallbacks);
806 nsresult rv = nsContentUtils::XPConnect()->
807 SetSecurityManagerForJSContext(cx, gWorkerSecurityManager, 0);
808 NS_ENSURE_SUCCESS(rv, nsnull);
810 return cx.forget();
813 #define LOOP_OVER_POOLS(_func, _args) \
814 PR_BEGIN_MACRO \
815 PRUint32 poolCount = mPools.Length(); \
816 for (PRUint32 i = 0; i < poolCount; i++) { \
817 mPools[i]-> _func _args ; \
819 PR_END_MACRO
821 void
822 nsDOMThreadService::CancelWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject)
824 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
825 LOOP_OVER_POOLS(CancelWorkersForGlobal, (aGlobalObject));
828 void
829 nsDOMThreadService::SuspendWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject)
831 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
832 LOOP_OVER_POOLS(SuspendWorkersForGlobal, (aGlobalObject));
835 void
836 nsDOMThreadService::ResumeWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject)
838 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
839 LOOP_OVER_POOLS(ResumeWorkersForGlobal, (aGlobalObject));
842 void
843 nsDOMThreadService::NoteDyingPool(nsDOMWorkerPool* aPool)
845 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
847 NS_ASSERTION(mPools.Contains(aPool), "aPool should be in the array!");
848 mPools.RemoveElement(aPool);
851 void
852 nsDOMThreadService::TimeoutReady(nsDOMWorkerTimeout* aTimeout)
854 nsRefPtr<nsDOMWorkerTimeoutRunnable> runnable =
855 new nsDOMWorkerTimeoutRunnable(aTimeout);
856 NS_ENSURE_TRUE(runnable,);
858 Dispatch(aTimeout->GetWorker(), runnable);
861 nsresult
862 nsDOMThreadService::ChangeThreadPoolMaxThreads(PRInt16 aDelta)
864 NS_ENSURE_ARG(aDelta == 1 || aDelta == -1);
866 PRUint32 currentThreadCount;
867 nsresult rv = mThreadPool->GetThreadLimit(&currentThreadCount);
868 NS_ENSURE_SUCCESS(rv, rv);
870 PRInt32 newThreadCount = (PRInt32)currentThreadCount + (PRInt32)aDelta;
871 NS_ASSERTION(newThreadCount >= THREADPOOL_MAX_THREADS,
872 "Can't go below initial thread count!");
874 if (newThreadCount > THREADPOOL_THREAD_CAP) {
875 NS_WARNING("Thread pool cap reached!");
876 return NS_ERROR_FAILURE;
879 rv = mThreadPool->SetThreadLimit((PRUint32)newThreadCount);
880 NS_ENSURE_SUCCESS(rv, rv);
882 return NS_OK;
885 nsIJSRuntimeService*
886 nsDOMThreadService::JSRuntimeService()
888 return gJSRuntimeService;
891 nsIThreadJSContextStack*
892 nsDOMThreadService::ThreadJSContextStack()
894 return gThreadJSContextStack;
897 nsIXPCSecurityManager*
898 nsDOMThreadService::WorkerSecurityManager()
900 return gWorkerSecurityManager;
904 * See nsIEventTarget
906 NS_IMETHODIMP
907 nsDOMThreadService::Dispatch(nsIRunnable* aEvent,
908 PRUint32 aFlags)
910 NS_ENSURE_ARG_POINTER(aEvent);
911 NS_ENSURE_FALSE(aFlags & NS_DISPATCH_SYNC, NS_ERROR_NOT_IMPLEMENTED);
913 // This should only ever be called by the timer code! We run the event right
914 // now, but all that does is queue the real event for the proper worker.
916 aEvent->Run();
918 return NS_OK;
922 * See nsIEventTarget
924 NS_IMETHODIMP
925 nsDOMThreadService::IsOnCurrentThread(PRBool* _retval)
927 NS_NOTREACHED("No one should call this!");
928 return NS_ERROR_NOT_IMPLEMENTED;
932 * See nsIObserver
934 NS_IMETHODIMP
935 nsDOMThreadService::Observe(nsISupports* aSubject,
936 const char* aTopic,
937 const PRUnichar* aData)
939 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
941 if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
942 Cleanup();
943 return NS_OK;
946 NS_NOTREACHED("Unknown observer topic!");
947 return NS_OK;
951 * See nsIThreadPoolListener
953 NS_IMETHODIMP
954 nsDOMThreadService::OnThreadCreated()
956 LOG(("Thread created"));
958 nsIThread* current = NS_GetCurrentThread();
960 // We want our worker threads to always have a lower priority than the main
961 // thread. NSPR docs say that this isn't incredibly reliable across all
962 // platforms but we hope for the best.
963 nsCOMPtr<nsISupportsPriority> priority(do_QueryInterface(current));
964 NS_ENSURE_TRUE(priority, NS_ERROR_FAILURE);
966 nsresult rv = priority->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
967 NS_ENSURE_SUCCESS(rv, rv);
969 NS_ASSERTION(gJSContextIndex != BAD_TLS_INDEX, "No context index!");
971 // Set the context up for the worker.
972 JSContext* cx = (JSContext*)PR_GetThreadPrivate(gJSContextIndex);
973 if (!cx) {
974 cx = nsDOMThreadService::CreateJSContext();
975 NS_ENSURE_TRUE(cx, NS_ERROR_FAILURE);
977 PRStatus status = PR_SetThreadPrivate(gJSContextIndex, cx);
978 if (status != PR_SUCCESS) {
979 NS_WARNING("Failed to set context on thread!");
980 nsContentUtils::XPConnect()->ReleaseJSContext(cx, PR_TRUE);
981 return NS_ERROR_FAILURE;
985 // Make sure that XPConnect knows about this context.
986 gThreadJSContextStack->Push(cx);
987 gThreadJSContextStack->SetSafeJSContext(cx);
989 return NS_OK;
992 NS_IMETHODIMP
993 nsDOMThreadService::OnThreadShuttingDown()
995 LOG(("Thread shutting down"));
997 NS_ASSERTION(gJSContextIndex != BAD_TLS_INDEX, "No context index!");
999 JSContext* cx = (JSContext*)PR_GetThreadPrivate(gJSContextIndex);
1000 NS_WARN_IF_FALSE(cx, "Thread died with no context?");
1001 if (cx) {
1002 JSContext* pushedCx;
1003 gThreadJSContextStack->Pop(&pushedCx);
1004 NS_ASSERTION(pushedCx == cx, "Popped the wrong context!");
1006 gThreadJSContextStack->SetSafeJSContext(nsnull);
1008 nsContentUtils::XPConnect()->ReleaseJSContext(cx, PR_TRUE);
1011 return NS_OK;
1015 * See nsIDOMThreadService
1017 NS_IMETHODIMP
1018 nsDOMThreadService::CreatePool(nsIDOMWorkerPool** _retval)
1020 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1022 NS_ENSURE_TRUE(mThreadPool, NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
1024 nsIDOMDocument* domDocument = nsContentUtils::GetDocumentFromCaller();
1025 NS_ENSURE_TRUE(domDocument, NS_ERROR_UNEXPECTED);
1027 nsCOMPtr<nsIDocument> callingDocument(do_QueryInterface(domDocument));
1028 NS_ENSURE_TRUE(callingDocument, NS_ERROR_NO_INTERFACE);
1030 nsRefPtr<nsDOMWorkerPool> pool(new nsDOMWorkerPool(callingDocument));
1031 NS_ENSURE_TRUE(pool, NS_ERROR_OUT_OF_MEMORY);
1033 nsresult rv = pool->Init();
1034 NS_ENSURE_SUCCESS(rv, rv);
1036 NS_ASSERTION(!mPools.Contains(pool), "Um?!");
1037 mPools.AppendElement(pool);
1039 NS_ADDREF(*_retval = pool);
1040 return NS_OK;