--- a/dom/public/idl/threads/nsIDOMThreads.idl
+++ b/dom/public/idl/threads/nsIDOMThreads.idl
@@ -117,16 +117,25 @@ interface nsIDOMWorkerPool : nsISupports
/**
* Create a new worker object by evaluating the given script.
*
* @param aSourceScript (in DOMString)
* The script to compile. See below for details on the scope in which
* the script will run.
*/
nsIDOMWorkerThread createWorker(in DOMString aSourceScript);
+
+ /**
+ * Create a new worker object by evaluating the given script.
+ *
+ * @param aSourceURL (in AString)
+ * The script url to load and compile. See below for details on the
+ * scope in which the script will run.
+ */
+ nsIDOMWorkerThread createWorkerFromURL(in AString aSourceURL);
};
[scriptable, uuid(0f2a52ea-afc9-49e6-86dd-2d0cb65b5dd5)]
interface nsIDOMThreadService : nsISupports
{
/**
* Creates a new DOM worker pool.
*/
--- a/dom/src/threads/Makefile.in
+++ b/dom/src/threads/Makefile.in
@@ -48,27 +48,29 @@ LIBRARY_NAME = domthreads_s
LIBXUL_LIBRARY = 1
FORCE_STATIC_LIB = 1
REQUIRES = \
caps \
content \
js \
layout \
+ necko \
pref \
string \
widget \
xpcom \
xpconnect \
$(NULL)
CPPSRCS = \
nsDOMThreadService.cpp \
nsDOMWorkerBase.cpp \
nsDOMWorkerPool.cpp \
+ nsDOMWorkerScriptLoader.cpp \
nsDOMWorkerSecurityManager.cpp \
nsDOMWorkerThread.cpp \
nsDOMWorkerTimeout.cpp \
$(NULL)
LOCAL_INCLUDES = \
-I$(topsrcdir)/dom/src/base \
$(NULL)
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/nsAutoJSObjectHolder.h
@@ -0,0 +1,160 @@
+/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is worker threads.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Ben Turner <bent.mozilla@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef __NSAUTOJSOBJECTHOLDER_H__
+#define __NSAUTOJSOBJECTHOLDER_H__
+
+#include "jsapi.h"
+
+/**
+ * Simple class that looks and acts like a JSObject* except that it unroots
+ * itself automatically if Root() is ever called. Designed to be rooted on the
+ * context or runtime (but not both!). Also automatically nulls its JSObject*
+ * on Unroot and asserts that Root has been called prior to assigning an object.
+ */
+class nsAutoJSObjectHolder
+{
+public:
+ /**
+ * Default constructor, no holding.
+ */
+ nsAutoJSObjectHolder()
+ : mRt(NULL), mObj(NULL), mHeld(PR_FALSE) { }
+
+ /**
+ * Hold by rooting on the context's runtime in the constructor, passing the
+ * result out.
+ */
+ nsAutoJSObjectHolder(JSContext* aCx, JSBool* aRv = NULL,
+ JSObject* aObj = NULL)
+ : mRt(NULL), mObj(aObj), mHeld(JS_FALSE) {
+ JSBool rv = Hold(aCx);
+ if (aRv) {
+ *aRv = rv;
+ }
+ }
+
+ /**
+ * Hold by rooting on the runtime in the constructor, passing the result out.
+ */
+ nsAutoJSObjectHolder(JSRuntime* aRt, JSBool* aRv = NULL,
+ JSObject* aObj = NULL)
+ : mRt(aRt), mObj(aObj), mHeld(JS_FALSE) {
+ JSBool rv = Hold(aRt);
+ if (aRv) {
+ *aRv = rv;
+ }
+ }
+
+ /**
+ * Always release on destruction.
+ */
+ ~nsAutoJSObjectHolder() {
+ Release();
+ }
+
+ /**
+ * Hold by rooting on the context's runtime.
+ */
+ JSBool Hold(JSContext* aCx) {
+ return Hold(JS_GetRuntime(aCx));
+ }
+
+ /**
+ * Hold by rooting on the runtime.
+ */
+ JSBool Hold(JSRuntime* aRt) {
+ if (!mHeld) {
+ mHeld = JS_AddNamedRootRT(aRt, &mObj, "nsAutoRootedJSObject");
+ if (mHeld) {
+ mRt = aRt;
+ }
+ }
+ return mHeld;
+ }
+
+ /**
+ * Manually release.
+ */
+ void Release() {
+ NS_ASSERTION(!mHeld || mRt, "Bad!");
+ if (mHeld) {
+ mHeld = !JS_RemoveRootRT(mRt, &mObj);
+ if (!mHeld) {
+ mRt = NULL;
+ }
+ mObj = NULL;
+ }
+ }
+
+ /**
+ * Determine if Hold has been called.
+ */
+ JSBool IsHeld() {
+ return mHeld;
+ }
+
+ /**
+ * Pretend to be a JSObject*.
+ */
+ JSObject* get() const {
+ return mObj;
+ }
+
+ /**
+ * Pretend to be a JSObject*.
+ */
+ operator JSObject*() const {
+ return get();
+ }
+
+ /**
+ * Pretend to be a JSObject*. Assert if not held.
+ */
+ JSObject* operator=(JSObject* aOther) {
+ NS_ASSERTION(mHeld, "Not rooted!");
+ return mObj = aOther;
+ }
+
+private:
+ JSRuntime* mRt;
+ JSObject* mObj;
+ JSBool mHeld;
+};
+
+#endif /* __NSAUTOJSOBJECTHOLDER_H__ */
--- a/dom/src/threads/nsDOMThreadService.cpp
+++ b/dom/src/threads/nsDOMThreadService.cpp
@@ -37,16 +37,18 @@
*
* ***** END LICENSE BLOCK ***** */
#include "nsDOMThreadService.h"
// Interfaces
#include "nsIComponentManager.h"
#include "nsIConsoleService.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
#include "nsIEventTarget.h"
#include "nsIGenericFactory.h"
#include "nsIJSContextStack.h"
#include "nsIJSRuntimeService.h"
#include "nsIObserverService.h"
#include "nsIScriptError.h"
#include "nsIScriptGlobalObject.h"
#include "nsIServiceManager.h"
@@ -83,16 +85,24 @@ PRLogModuleInfo *gDOMThreadsLog = nsnull
PR_STATIC_ASSERT(THREADPOOL_MAX_THREADS >= 1);
// The maximum number of idle threads in the internal thread pool
#define THREADPOOL_IDLE_THREADS 3
PR_STATIC_ASSERT(THREADPOOL_MAX_THREADS >= THREADPOOL_IDLE_THREADS);
+// As we suspend threads for various reasons (navigating away from the page,
+// loading scripts, etc.) we open another slot in the thread pool for another
+// worker to use. We can't do this forever so we set an absolute cap on the
+// number of threads we'll allow to prevent DOS attacks.
+#define THREADPOOL_THREAD_CAP 20
+
+PR_STATIC_ASSERT(THREADPOOL_THREAD_CAP >= THREADPOOL_MAX_THREADS);
+
// The number of times our JS operation callback will be called before yielding
// the thread
#define CALLBACK_YIELD_THRESHOLD 100
// A "bad" value for the NSPR TLS functions.
#define BAD_TLS_INDEX (PRUintn)-1
// Don't know why nsISupports.idl defines this out...
@@ -381,37 +391,42 @@ DOMWorkerOperationCallback(JSContext* aC
{
nsDOMWorkerThread* worker = (nsDOMWorkerThread*)JS_GetContextPrivate(aCx);
// Want a strong ref here to make sure that the monitor we wait on won't go
// away.
nsRefPtr<nsDOMWorkerPool> pool;
PRBool wasSuspended = PR_FALSE;
+ PRBool extraThreadAllowed = PR_FALSE;
jsrefcount suspendDepth = 0;
while (1) {
// Kill execution if we're canceled.
if (worker->IsCanceled()) {
LOG(("Forcefully killing JS for worker [0x%p]",
static_cast<void*>(worker)));
if (wasSuspended) {
- gDOMThreadService->ChangeThreadPoolMaxThreads(-1);
+ if (extraThreadAllowed) {
+ gDOMThreadService->ChangeThreadPoolMaxThreads(-1);
+ }
JS_ResumeRequest(aCx, suspendDepth);
}
// Kill exectuion of the currently running JS.
return PR_FALSE;
}
// Break out if we're not suspended.
if (!worker->IsSuspended()) {
if (wasSuspended) {
- gDOMThreadService->ChangeThreadPoolMaxThreads(-1);
+ if (extraThreadAllowed) {
+ gDOMThreadService->ChangeThreadPoolMaxThreads(-1);
+ }
JS_ResumeRequest(aCx, suspendDepth);
}
break;
}
if (!wasSuspended) {
// Make sure we can get the monitor we need to wait on. It's possible that
// the worker was canceled since we checked above.
@@ -422,18 +437,20 @@ DOMWorkerOperationCallback(JSContext* aC
pool = worker->Pool();
// Make sure to suspend our request while we block like this, otherwise we
// prevent GC for everyone.
suspendDepth = JS_SuspendRequest(aCx);
// Since we're going to block this thread we should open up a new thread
- // in the thread pool for other workers.
- gDOMThreadService->ChangeThreadPoolMaxThreads(1);
+ // in the thread pool for other workers. Must check the return value to
+ // make sure we don't decrement when we failed.
+ extraThreadAllowed =
+ NS_SUCCEEDED(gDOMThreadService->ChangeThreadPoolMaxThreads(1));
// Only do all this setup once.
wasSuspended = PR_TRUE;
}
nsAutoMonitor mon(pool->Monitor());
mon.Wait();
}
@@ -760,16 +777,24 @@ nsDOMThreadService::CreateJSContext()
JSAutoContextDestroyer cx(JS_NewContext(rt, 8192));
NS_ENSURE_TRUE(cx, nsnull);
JS_SetErrorReporter(cx, DOMWorkerErrorReporter);
JS_SetOperationCallback(cx, DOMWorkerOperationCallback,
100 * JS_OPERATION_WEIGHT_BASE);
+ static JSSecurityCallbacks securityCallbacks = {
+ nsDOMWorkerSecurityManager::JSCheckAccess,
+ NULL,
+ NULL
+ };
+
+ JS_SetSecurityCallbacks(cx, &securityCallbacks);
+
nsresult rv = nsContentUtils::XPConnect()->
SetSecurityManagerForJSContext(cx, gWorkerSecurityManager, 0);
NS_ENSURE_SUCCESS(rv, nsnull);
return cx.forget();
}
#define LOOP_OVER_POOLS(_func, _args) \
@@ -828,16 +853,21 @@ nsDOMThreadService::ChangeThreadPoolMaxT
PRUint32 currentThreadCount;
nsresult rv = mThreadPool->GetThreadLimit(¤tThreadCount);
NS_ENSURE_SUCCESS(rv, rv);
PRInt32 newThreadCount = (PRInt32)currentThreadCount + (PRInt32)aDelta;
NS_ASSERTION(newThreadCount >= THREADPOOL_MAX_THREADS,
"Can't go below initial thread count!");
+ if (newThreadCount > THREADPOOL_THREAD_CAP) {
+ NS_WARNING("Thread pool cap reached!");
+ return NS_ERROR_FAILURE;
+ }
+
rv = mThreadPool->SetThreadLimit((PRUint32)newThreadCount);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsIJSRuntimeService*
nsDOMThreadService::JSRuntimeService()
@@ -973,17 +1003,23 @@ nsDOMThreadService::OnThreadShuttingDown
*/
NS_IMETHODIMP
nsDOMThreadService::CreatePool(nsIDOMWorkerPool** _retval)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ENSURE_TRUE(mThreadPool, NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
- nsRefPtr<nsDOMWorkerPool> pool(new nsDOMWorkerPool());
+ nsIDOMDocument* domDocument = nsContentUtils::GetDocumentFromCaller();
+ NS_ENSURE_TRUE(domDocument, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIDocument> callingDocument(do_QueryInterface(domDocument));
+ NS_ENSURE_TRUE(callingDocument, NS_ERROR_NO_INTERFACE);
+
+ nsRefPtr<nsDOMWorkerPool> pool(new nsDOMWorkerPool(callingDocument));
NS_ENSURE_TRUE(pool, NS_ERROR_OUT_OF_MEMORY);
nsresult rv = pool->Init();
NS_ENSURE_SUCCESS(rv, rv);
NS_ASSERTION(!mPools.Contains(pool), "Um?!");
mPools.AppendElement(pool);
--- a/dom/src/threads/nsDOMWorkerPool.cpp
+++ b/dom/src/threads/nsDOMWorkerPool.cpp
@@ -64,20 +64,22 @@
#define LOOP_OVER_WORKERS(_func, _args) \
PR_BEGIN_MACRO \
PRUint32 workerCount = mWorkers.Length(); \
for (PRUint32 i = 0; i < workerCount; i++) { \
mWorkers[i]-> _func _args ; \
} \
PR_END_MACRO
-nsDOMWorkerPool::nsDOMWorkerPool()
-: mParentGlobal(nsnull)
+nsDOMWorkerPool::nsDOMWorkerPool(nsIDocument* aDocument)
+: mParentGlobal(nsnull),
+ mParentDocument(aDocument)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+ NS_ASSERTION(aDocument, "Must have a document!");
}
nsDOMWorkerPool::~nsDOMWorkerPool()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
LOOP_OVER_WORKERS(Cancel, ());
@@ -229,16 +231,24 @@ nsDOMWorkerPool::ResumeWorkersForGlobal(
LOOP_OVER_WORKERS(Resume, ());
Resume();
nsAutoMonitor mon(mMonitor);
mon.NotifyAll();
}
}
+nsIDocument*
+nsDOMWorkerPool::GetParentDocument()
+{
+ NS_ASSERTION(NS_IsMainThread(),
+ "Don't touch the non-threadsafe document off the main thread!");
+ return mParentDocument;
+}
+
NS_IMETHODIMP
nsDOMWorkerPool::PostMessage(const nsAString& aMessage)
{
nsresult rv = PostMessageInternal(aMessage);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
@@ -271,28 +281,52 @@ NS_IMETHODIMP
nsDOMWorkerPool::GetErrorListener(nsIDOMWorkerErrorListener** aListener)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_IF_ADDREF(*aListener = mErrorListener);
return NS_OK;
}
NS_IMETHODIMP
-nsDOMWorkerPool::CreateWorker(const nsAString& fullScript,
+nsDOMWorkerPool::CreateWorker(const nsAString& aFullScript,
nsIDOMWorkerThread** _retval)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
- NS_ENSURE_ARG(!fullScript.IsEmpty());
+ NS_ENSURE_ARG(!aFullScript.IsEmpty());
NS_ENSURE_ARG_POINTER(_retval);
- nsRefPtr<nsDOMWorkerThread> worker(new nsDOMWorkerThread(this, fullScript));
+ nsRefPtr<nsDOMWorkerThread> worker =
+ new nsDOMWorkerThread(this, aFullScript, PR_FALSE);
NS_ENSURE_TRUE(worker, NS_ERROR_OUT_OF_MEMORY);
nsresult rv = worker->Init();
NS_ENSURE_SUCCESS(rv, rv);
NS_ASSERTION(!mWorkers.Contains(worker), "Um?!");
mWorkers.AppendElement(worker);
NS_ADDREF(*_retval = worker);
return NS_OK;
}
+
+NS_IMETHODIMP
+nsDOMWorkerPool::CreateWorkerFromURL(const nsAString& aScriptURL,
+ nsIDOMWorkerThread** _retval)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+ NS_ENSURE_ARG(!aScriptURL.IsEmpty());
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsRefPtr<nsDOMWorkerThread> worker =
+ new nsDOMWorkerThread(this, aScriptURL, PR_TRUE);
+ NS_ENSURE_TRUE(worker, NS_ERROR_OUT_OF_MEMORY);
+
+ nsresult rv = worker->Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(!mWorkers.Contains(worker), "Um?!");
+ mWorkers.AppendElement(worker);
+
+ NS_ADDREF(*_retval = worker);
+ return NS_OK;
+}
--- a/dom/src/threads/nsDOMWorkerPool.h
+++ b/dom/src/threads/nsDOMWorkerPool.h
@@ -47,39 +47,42 @@
// Other includes
#include "jsapi.h"
#include "nsStringGlue.h"
#include "nsTPtrArray.h"
#include "prmon.h"
class nsDOMWorkerThread;
+class nsIDocument;
class nsIScriptError;
class nsIScriptGlobalObject;
/**
* The pool is almost always touched only on the main thread.
*/
class nsDOMWorkerPool : public nsDOMWorkerBase,
public nsIDOMWorkerPool,
public nsIClassInfo
{
friend class nsDOMThreadService;
friend class nsDOMWorkerFunctions;
friend class nsDOMWorkerPoolWeakRef;
+ friend class nsDOMWorkerScriptLoader;
+ friend class nsDOMWorkerStreamObserver;
friend class nsDOMWorkerThread;
friend class nsReportErrorRunnable;
friend JSBool DOMWorkerOperationCallback(JSContext* aCx);
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIDOMWORKERPOOL
NS_DECL_NSICLASSINFO
- nsDOMWorkerPool();
+ nsDOMWorkerPool(nsIDocument* aDocument);
// For nsDOMWorkerBase
virtual nsDOMWorkerPool* Pool() {
return this;
}
private:
virtual ~nsDOMWorkerPool();
@@ -101,19 +104,24 @@ private:
void CancelWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject);
void SuspendWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject);
void ResumeWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject);
PRMonitor* Monitor() {
return mMonitor;
}
+ nsIDocument* GetParentDocument();
+
// Weak reference to the window that created and owns this pool.
nsISupports* mParentGlobal;
+ // Weak reference to the document that created this pool.
+ nsIDocument* mParentDocument;
+
// Weak array of workers. The idea is that workers can be garbage collected
// independently of the owning pool and other workers.
nsTPtrArray<nsDOMWorkerThread> mWorkers;
// An error handler function, may be null.
nsCOMPtr<nsIDOMWorkerErrorListener> mErrorListener;
// Monitor for suspending and resuming workers.
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/nsDOMWorkerScriptLoader.cpp
@@ -0,0 +1,790 @@
+/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is worker threads.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Ben Turner <bent.mozilla@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsDOMWorkerScriptLoader.h"
+
+// Interfaces
+#include "nsIContentPolicy.h"
+#include "nsIIOService.h"
+#include "nsIRequest.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIStreamLoader.h"
+
+// Other includes
+#include "nsAutoLock.h"
+#include "nsContentErrors.h"
+#include "nsContentPolicyUtils.h"
+#include "nsContentUtils.h"
+#include "nsISupportsPrimitives.h"
+#include "nsNetError.h"
+#include "nsNetUtil.h"
+#include "nsScriptLoader.h"
+#include "nsThreadUtils.h"
+#include "pratom.h"
+
+// DOMWorker includes
+#include "nsDOMWorkerPool.h"
+#include "nsDOMThreadService.h"
+#include "nsDOMWorkerTimeout.h"
+
+#define LOG(_args) PR_LOG(gDOMThreadsLog, PR_LOG_DEBUG, _args)
+
+nsDOMWorkerScriptLoader::nsDOMWorkerScriptLoader()
+: mWorker(nsnull),
+ mTarget(nsnull),
+ mCx(NULL),
+ mScriptCount(0),
+ mCanceled(PR_FALSE),
+ mTrackedByWorker(PR_FALSE)
+{
+ // Created on worker thread.
+ NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+}
+
+nsDOMWorkerScriptLoader::~nsDOMWorkerScriptLoader()
+{
+ // Can't touch mWorker's lock
+ if (!mCanceled) {
+ // Destroyed on worker thread, unless canceled (and then who knows!).
+ NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+ if (mTrackedByWorker) {
+ jsrefcount suspendDepth;
+ if (mCx) {
+ suspendDepth = JS_SuspendRequest(mCx);
+ }
+
+ nsAutoLock lock(mWorker->Lock());
+ #ifdef DEBUG
+ PRBool removed =
+ #endif
+ mWorker->mScriptLoaders.RemoveElement(this);
+ NS_ASSERTION(removed, "Something is wrong here!");
+
+ if (mCx) {
+ JS_ResumeRequest(mCx, suspendDepth);
+ }
+ }
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED1(nsDOMWorkerScriptLoader, nsRunnable,
+ nsIStreamLoaderObserver)
+
+nsresult
+nsDOMWorkerScriptLoader::LoadScripts(nsDOMWorkerThread* aWorker,
+ JSContext* aCx,
+ const nsTArray<nsString>& aURLs)
+{
+ NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+ NS_ASSERTION(aWorker, "Null worker!");
+ NS_ASSERTION(aCx, "Null context!");
+
+ NS_ASSERTION(!mWorker, "Not designed to be used more than once!");
+
+ mWorker = aWorker;
+ mCx = aCx;
+
+ mTarget = NS_GetCurrentThread();
+ NS_ASSERTION(mTarget, "This should never be null!");
+
+ {
+ JSAutoSuspendRequest asr(aCx);
+ nsAutoLock lock(mWorker->Lock());
+ mTrackedByWorker = nsnull != mWorker->mScriptLoaders.AppendElement(this);
+ NS_ASSERTION(mTrackedByWorker, "Failed to add loader to worker's array!");
+ }
+
+ if (mCanceled) {
+ return NS_ERROR_ABORT;
+ }
+
+ mScriptCount = aURLs.Length();
+ if (!mScriptCount) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Do all the memory work for these arrays now rather than checking for
+ // failures all along the way.
+ PRBool success = mLoadInfos.SetCapacity(mScriptCount);
+ NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
+
+ // Need one runnable per script and then an extra for the finished
+ // notification.
+ success = mPendingRunnables.SetCapacity(mScriptCount + 1);
+ NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
+
+ for (PRUint32 index = 0; index < mScriptCount; index++) {
+ ScriptLoadInfo* newInfo = mLoadInfos.AppendElement();
+ NS_ASSERTION(newInfo, "Shouldn't fail if SetCapacity succeeded above!");
+
+ newInfo->url.Assign(aURLs[index]);
+ if (newInfo->url.IsEmpty()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ success = newInfo->scriptObj.Hold(aCx);
+ NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
+ }
+
+ // Don't want timeouts, etc., from queuing up while we're waiting on the
+ // network or compiling.
+ AutoSuspendWorkerEvents aswe(this);
+
+ nsresult rv = DoRunLoop();
+
+ {
+ JSAutoSuspendRequest asr(aCx);
+ nsAutoLock lock(mWorker->Lock());
+#ifdef DEBUG
+ PRBool removed =
+#endif
+ mWorker->mScriptLoaders.RemoveElement(this);
+ NS_ASSERTION(removed, "Something is wrong here!");
+ mTrackedByWorker = PR_FALSE;
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Verify that all scripts downloaded and compiled.
+ rv = VerifyScripts();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ExecuteScripts();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsDOMWorkerScriptLoader::LoadScript(nsDOMWorkerThread* aWorker,
+ JSContext* aCx,
+ const nsString& aURL)
+{
+ nsAutoTArray<nsString, 1> url;
+ url.AppendElement(aURL);
+
+ return LoadScripts(aWorker, aCx, url);
+}
+
+nsresult
+nsDOMWorkerScriptLoader::DoRunLoop()
+{
+ NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+ volatile PRBool done = PR_FALSE;
+ mDoneRunnable = new ScriptLoaderDone(this, &done);
+ NS_ENSURE_TRUE(mDoneRunnable, NS_ERROR_OUT_OF_MEMORY);
+
+ nsresult rv = NS_DispatchToMainThread(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!(done || mCanceled)) {
+ // Since we're going to lock up this thread we might as well allow the
+ // thread service to schedule another worker on a new thread.
+ nsDOMThreadService* threadService = nsDOMThreadService::get();
+ PRBool changed = NS_SUCCEEDED(threadService->ChangeThreadPoolMaxThreads(1));
+
+ while (!(done || mCanceled)) {
+ JSAutoSuspendRequest asr(mCx);
+ NS_ProcessNextEvent(mTarget);
+ }
+
+ if (changed) {
+ threadService->ChangeThreadPoolMaxThreads(-1);
+ }
+ }
+
+ return mCanceled ? NS_ERROR_ABORT : NS_OK;
+}
+
+nsresult
+nsDOMWorkerScriptLoader::VerifyScripts()
+{
+ nsresult rv = NS_OK;
+
+ for (PRUint32 index = 0; index < mScriptCount; index++) {
+ ScriptLoadInfo& loadInfo = mLoadInfos[index];
+ NS_ASSERTION(loadInfo.done, "Inconsistent state!");
+
+ if (NS_SUCCEEDED(loadInfo.result) && loadInfo.scriptObj) {
+ continue;
+ }
+
+ NS_ASSERTION(!loadInfo.scriptObj, "Inconsistent state!");
+
+ // Flag failure before worrying about whether or not to report an error.
+ rv = NS_FAILED(loadInfo.result) ? loadInfo.result : NS_ERROR_FAILURE;
+
+ // If loadInfo.result is a success code then the compiler probably reported
+ // an error already. Also we don't really care about NS_BINDING_ABORTED
+ // since that's the code we set when some other script had a problem and the
+ // rest were canceled.
+ if (NS_SUCCEEDED(loadInfo.result) || loadInfo.result == NS_BINDING_ABORTED) {
+ continue;
+ }
+
+ // Ok, this is the script that caused us to fail.
+
+ // Only throw an error there is no other pending exception.
+ if (!JS_IsExceptionPending(mCx)) {
+ NS_ConvertUTF16toUTF8 url(loadInfo.url);
+ JS_ReportError(mCx, "Failed to compile script: %s", url.get());
+ }
+ break;
+ }
+
+ return rv;
+}
+
+nsresult
+nsDOMWorkerScriptLoader::ExecuteScripts()
+{
+ // Now execute all the scripts.
+ for (PRUint32 index = 0; index < mScriptCount; index++) {
+ ScriptLoadInfo& loadInfo = mLoadInfos[index];
+
+ JSScript* script =
+ static_cast<JSScript*>(JS_GetPrivate(mCx, loadInfo.scriptObj));
+ NS_ASSERTION(script, "This shouldn't ever be null!");
+
+ JSObject* global = mWorker->mGlobal ?
+ mWorker->mGlobal :
+ JS_GetGlobalObject(mCx);
+ NS_ENSURE_STATE(global);
+
+ // Because we may have nested calls to this function we don't want the
+ // execution to automatically report errors. We let them propagate instead.
+ uint32 oldOpts =
+ JS_SetOptions(mCx, JS_GetOptions(mCx) | JSOPTION_DONT_REPORT_UNCAUGHT);
+
+ jsval val;
+ PRBool success = JS_ExecuteScript(mCx, global, script, &val);
+
+ JS_SetOptions(mCx, oldOpts);
+
+ if (!success) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ return NS_OK;
+}
+
+void
+nsDOMWorkerScriptLoader::Cancel()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+ NS_ASSERTION(!mCanceled, "Cancel called more than once!");
+ mCanceled = PR_TRUE;
+
+ for (PRUint32 index = 0; index < mScriptCount; index++) {
+ ScriptLoadInfo& loadInfo = mLoadInfos[index];
+
+ nsIRequest* request =
+ static_cast<nsIRequest*>(loadInfo.channel.get());
+ if (request) {
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ request->Cancel(NS_BINDING_ABORTED);
+ NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to cancel channel!");
+ }
+ }
+
+ nsAutoTArray<ScriptLoaderRunnable*, 10> runnables;
+ {
+ nsAutoLock lock(mWorker->Lock());
+ runnables.AppendElements(mPendingRunnables);
+ mPendingRunnables.Clear();
+ }
+
+ PRUint32 runnableCount = runnables.Length();
+ for (PRUint32 index = 0; index < runnableCount; index++) {
+ runnables[index]->Revoke();
+ }
+
+ // We're about to post a revoked event to the worker thread, which seems
+ // silly, but we have to do this because the worker thread may be sleeping
+ // waiting on its event queue.
+ NotifyDone();
+}
+
+NS_IMETHODIMP
+nsDOMWorkerScriptLoader::Run()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+ // We may have been canceled already.
+ if (mCanceled) {
+ return NS_BINDING_ABORTED;
+ }
+
+ nsresult rv = RunInternal();
+ if (NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+
+ // Ok, something failed beyond a normal cancel.
+
+ // If necko is holding a ref to us then we'll end up notifying in the
+ // OnStreamComplete method, not here.
+ PRBool needsNotify = PR_TRUE;
+
+ // Cancel any async channels that were already opened.
+ for (PRUint32 index = 0; index < mScriptCount; index++) {
+ ScriptLoadInfo& loadInfo = mLoadInfos[index];
+
+ nsIRequest* request = static_cast<nsIRequest*>(loadInfo.channel.get());
+ if (request) {
+#ifdef DEBUG
+ nsresult rvInner =
+#endif
+ request->Cancel(NS_BINDING_ABORTED);
+ NS_WARN_IF_FALSE(NS_SUCCEEDED(rvInner), "Failed to cancel channel!");
+
+ // Necko is holding a ref to us so make sure that the OnStreamComplete
+ // code sends the done event.
+ needsNotify = PR_FALSE;
+ }
+ else {
+ // Make sure to set this so that the OnStreamComplete code will dispatch
+ // the done event.
+ loadInfo.done = PR_TRUE;
+ }
+ }
+
+ if (needsNotify) {
+ NotifyDone();
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDOMWorkerScriptLoader::OnStreamComplete(nsIStreamLoader* aLoader,
+ nsISupports* aContext,
+ nsresult aStatus,
+ PRUint32 aStringLen,
+ const PRUint8* aString)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+ // We may have been canceled already.
+ if (mCanceled) {
+ return NS_BINDING_ABORTED;
+ }
+
+ nsresult rv = OnStreamCompleteInternal(aLoader, aContext, aStatus, aStringLen,
+ aString);
+
+ // Dispatch the done event if we've received all the data.
+ for (PRUint32 index = 0; index < mScriptCount; index++) {
+ if (!mLoadInfos[index].done) {
+ // Some async load is still outstanding, don't notify yet.
+ break;
+ }
+
+ if (index == mScriptCount - 1) {
+ // All loads complete, signal the thread.
+ NotifyDone();
+ }
+ }
+
+ return rv;
+}
+
+nsresult
+nsDOMWorkerScriptLoader::RunInternal()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+ // Things we need to make all this work...
+ nsIDocument* parentDoc = mWorker->Pool()->GetParentDocument();
+ NS_ASSERTION(parentDoc, "Null parent document?!");
+
+ // All of these can potentially be null, but that should be ok. We'll either
+ // succeed without them or fail below.
+ nsIURI* parentBaseURI = parentDoc->GetBaseURI();
+ nsCOMPtr<nsILoadGroup> loadGroup(parentDoc->GetDocumentLoadGroup());
+ nsCOMPtr<nsIIOService> ios(do_GetIOService());
+
+ for (PRUint32 index = 0; index < mScriptCount; index++) {
+ ScriptLoadInfo& loadInfo = mLoadInfos[index];
+ nsresult& rv = loadInfo.result;
+
+ nsCOMPtr<nsIURI>& uri = loadInfo.finalURI;
+ rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri),
+ loadInfo.url, parentDoc,
+ parentBaseURI);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
+ NS_ENSURE_TRUE(secMan, NS_ERROR_FAILURE);
+
+ rv =
+ secMan->CheckLoadURIWithPrincipal(parentDoc->NodePrincipal(), uri,
+ nsIScriptSecurityManager::ALLOW_CHROME);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ PRInt16 shouldLoad = nsIContentPolicy::ACCEPT;
+ rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_SCRIPT,
+ uri,
+ parentDoc->NodePrincipal(),
+ parentDoc,
+ NS_LITERAL_CSTRING("text/javascript"),
+ nsnull,
+ &shouldLoad,
+ nsContentUtils::GetContentPolicy(),
+ secMan);
+ if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
+ if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) {
+ return NS_ERROR_CONTENT_BLOCKED;
+ }
+ return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT;
+ }
+
+ // We need to know which index we're on in OnStreamComplete so we know where
+ // to put the result.
+ nsCOMPtr<nsISupportsPRUint32> indexSupports =
+ do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = indexSupports->SetData(index);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We don't care about progress so just use the simple stream loader for
+ // OnStreamComplete notification only.
+ nsCOMPtr<nsIStreamLoader> loader;
+ rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewChannel(getter_AddRefs(loadInfo.channel), uri, ios, loadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = loadInfo.channel->AsyncOpen(loader, indexSupports);
+ if (NS_FAILED(rv)) {
+ // Null this out so we don't try to cancel it later.
+ loadInfo.channel = nsnull;
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsDOMWorkerScriptLoader::OnStreamCompleteInternal(nsIStreamLoader* aLoader,
+ nsISupports* aContext,
+ nsresult aStatus,
+ PRUint32 aStringLen,
+ const PRUint8* aString)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+ nsCOMPtr<nsISupportsPRUint32> indexSupports(do_QueryInterface(aContext));
+ NS_ENSURE_TRUE(indexSupports, NS_ERROR_NO_INTERFACE);
+
+ PRUint32 index = PR_UINT32_MAX;
+ indexSupports->GetData(&index);
+
+ if (index >= mScriptCount) {
+ NS_NOTREACHED("This really can't fail or we'll hang!");
+ return NS_ERROR_FAILURE;
+ }
+
+ ScriptLoadInfo& loadInfo = mLoadInfos[index];
+
+ NS_ASSERTION(!loadInfo.done, "Got complete on the same load twice!");
+ loadInfo.done = PR_TRUE;
+
+#ifdef DEBUG
+ // Make sure we're seeing the channel that we expect.
+ nsCOMPtr<nsIRequest> request;
+ nsresult rvDebug = aLoader->GetRequest(getter_AddRefs(request));
+
+ // When we cancel sometimes we get null here. That should be ok, but only if
+ // we're canceled.
+ NS_ASSERTION(NS_SUCCEEDED(rvDebug) || mCanceled, "GetRequest failed!");
+
+ if (NS_SUCCEEDED(rvDebug)) {
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
+ NS_ASSERTION(channel, "QI failed!");
+
+ nsCOMPtr<nsISupports> thisChannel(do_QueryInterface(channel));
+ NS_ASSERTION(thisChannel, "QI failed!");
+
+ nsCOMPtr<nsISupports> ourChannel(do_QueryInterface(loadInfo.channel));
+ NS_ASSERTION(ourChannel, "QI failed!");
+
+ NS_ASSERTION(thisChannel == ourChannel, "Wrong channel!");
+ }
+#endif
+
+ // Use an alias to keep rv and loadInfo.result in sync.
+ nsresult& rv = loadInfo.result = aStatus;
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!(aStringLen && aString)) {
+ return rv = NS_ERROR_UNEXPECTED;
+ }
+
+ nsIDocument* parentDoc = mWorker->Pool()->GetParentDocument();
+ NS_ASSERTION(parentDoc, "Null parent document?!");
+
+ // Use the regular nsScriptLoader for this grunt work! Should be just fine
+ // because we're running on the main thread.
+ rv = nsScriptLoader::ConvertToUTF16(loadInfo.channel, aString, aStringLen,
+ EmptyString(), parentDoc,
+ loadInfo.scriptText);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (loadInfo.scriptText.IsEmpty()) {
+ return rv = NS_ERROR_FAILURE;
+ }
+
+ nsCString filename;
+ loadInfo.finalURI->GetSpec(filename);
+
+ if (filename.IsEmpty()) {
+ filename.Assign(NS_LossyConvertUTF16toASCII(loadInfo.url));
+ }
+ else {
+ // This will help callers figure out what their script url resolved to in
+ // case of errors.
+ loadInfo.url.Assign(NS_ConvertUTF8toUTF16(filename));
+ }
+
+ nsRefPtr<ScriptCompiler> compiler =
+ new ScriptCompiler(this, mCx, loadInfo.scriptText, filename,
+ loadInfo.scriptObj);
+ NS_ASSERTION(compiler, "Out of memory!");
+ if (!compiler) {
+ return rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ rv = mTarget->Dispatch(compiler, NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return rv;
+}
+
+void
+nsDOMWorkerScriptLoader::NotifyDone()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+ if (!mDoneRunnable) {
+ // We've already completed, no need to cancel anything.
+ return;
+ }
+
+ for (PRUint32 index = 0; index < mScriptCount; index++) {
+ ScriptLoadInfo& loadInfo = mLoadInfos[index];
+ // Null both of these out because they aren't threadsafe and must be
+ // destroyed on this thread.
+ loadInfo.channel = nsnull;
+ loadInfo.finalURI = nsnull;
+
+ if (mCanceled) {
+ // Simulate a complete, yet failed, load.
+ loadInfo.done = PR_TRUE;
+ loadInfo.result = NS_BINDING_ABORTED;
+ }
+ }
+
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ mTarget->Dispatch(mDoneRunnable, NS_DISPATCH_NORMAL);
+ NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Couldn't dispatch done event!");
+
+ mDoneRunnable = nsnull;
+}
+
+void
+nsDOMWorkerScriptLoader::SuspendWorkerEvents()
+{
+ NS_ASSERTION(mWorker, "No worker yet!");
+ mWorker->SuspendTimeouts();
+}
+
+void
+nsDOMWorkerScriptLoader::ResumeWorkerEvents()
+{
+ NS_ASSERTION(mWorker, "No worker yet!");
+ mWorker->ResumeTimeouts();
+}
+
+nsDOMWorkerScriptLoader::
+ScriptLoaderRunnable::ScriptLoaderRunnable(nsDOMWorkerScriptLoader* aLoader)
+: mRevoked(PR_FALSE),
+ mLoader(aLoader)
+{
+ nsAutoLock lock(aLoader->Lock());
+#ifdef DEBUG
+ nsDOMWorkerScriptLoader::ScriptLoaderRunnable** added =
+#endif
+ aLoader->mPendingRunnables.AppendElement(this);
+ NS_ASSERTION(added, "This shouldn't fail because we SetCapacity earlier!");
+}
+
+nsDOMWorkerScriptLoader::
+ScriptLoaderRunnable::~ScriptLoaderRunnable()
+{
+ if (!mRevoked) {
+ nsAutoLock lock(mLoader->Lock());
+#ifdef DEBUG
+ PRBool removed =
+#endif
+ mLoader->mPendingRunnables.RemoveElement(this);
+ NS_ASSERTION(removed, "Someone has changed the array!");
+ }
+}
+
+void
+nsDOMWorkerScriptLoader::ScriptLoaderRunnable::Revoke()
+{
+ mRevoked = PR_TRUE;
+}
+
+nsDOMWorkerScriptLoader::
+ScriptCompiler::ScriptCompiler(nsDOMWorkerScriptLoader* aLoader,
+ JSContext* aCx,
+ const nsString& aScriptText,
+ const nsCString& aFilename,
+ nsAutoJSObjectHolder& aScriptObj)
+: ScriptLoaderRunnable(aLoader),
+ mCx(aCx),
+ mScriptText(aScriptText),
+ mFilename(aFilename),
+ mScriptObj(aScriptObj)
+{
+ NS_ASSERTION(aCx, "Null context!");
+ NS_ASSERTION(!aScriptText.IsEmpty(), "No script to compile!");
+ NS_ASSERTION(aScriptObj.IsHeld(), "Should be held!");
+}
+
+NS_IMETHODIMP
+nsDOMWorkerScriptLoader::ScriptCompiler::Run()
+{
+ NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+ if (mRevoked) {
+ return NS_OK;
+ }
+
+ NS_ASSERTION(!mScriptObj, "Already have a script object?!");
+ NS_ASSERTION(mScriptObj.IsHeld(), "Not held?!");
+ NS_ASSERTION(!mScriptText.IsEmpty(), "Shouldn't have empty source here!");
+
+ JSAutoRequest ar(mCx);
+
+ JSObject* global = JS_GetGlobalObject(mCx);
+ NS_ENSURE_STATE(global);
+
+ // Because we may have nested calls to this function we don't want the
+ // execution to automatically report errors. We let them propagate instead.
+ uint32 oldOpts =
+ JS_SetOptions(mCx, JS_GetOptions(mCx) | JSOPTION_DONT_REPORT_UNCAUGHT);
+
+ JSScript* script = JS_CompileUCScript(mCx, global, mScriptText.BeginReading(),
+ mScriptText.Length(), mFilename.get(),
+ 1);
+ JS_SetOptions(mCx, oldOpts);
+
+ if (!script) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mScriptObj = JS_NewScriptObject(mCx, script);
+ NS_ENSURE_STATE(mScriptObj);
+
+ return NS_OK;
+}
+
+nsDOMWorkerScriptLoader::
+ScriptLoaderDone::ScriptLoaderDone(nsDOMWorkerScriptLoader* aLoader,
+ volatile PRBool* aDoneFlag)
+: ScriptLoaderRunnable(aLoader),
+ mDoneFlag(aDoneFlag)
+{
+ NS_ASSERTION(aDoneFlag && !*aDoneFlag, "Bad setup!");
+}
+
+NS_IMETHODIMP
+nsDOMWorkerScriptLoader::ScriptLoaderDone::Run()
+{
+ NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+ if (mRevoked) {
+ return NS_OK;
+ }
+
+ *mDoneFlag = PR_TRUE;
+ return NS_OK;
+}
+
+nsDOMWorkerScriptLoader::
+AutoSuspendWorkerEvents::AutoSuspendWorkerEvents(nsDOMWorkerScriptLoader* aLoader)
+: mLoader(aLoader)
+{
+ NS_ASSERTION(aLoader, "Don't hand me null!");
+ aLoader->SuspendWorkerEvents();
+}
+
+nsDOMWorkerScriptLoader::
+AutoSuspendWorkerEvents::~AutoSuspendWorkerEvents()
+{
+ mLoader->ResumeWorkerEvents();
+}
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/nsDOMWorkerScriptLoader.h
@@ -0,0 +1,224 @@
+/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is worker threads.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Ben Turner <bent.mozilla@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef __NSDOMWORKERSCRIPTLOADER_H__
+#define __NSDOMWORKERSCRIPTLOADER_H__
+
+// Bases
+#include "nsThreadUtils.h"
+#include "nsIStreamLoader.h"
+
+// Interfaces
+#include "nsIChannel.h"
+#include "nsIURI.h"
+
+// Other includes
+#include "jsapi.h"
+#include "nsAutoPtr.h"
+#include "nsAutoJSObjectHolder.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+#include "nsTArray.h"
+#include "prlock.h"
+
+// DOMWorker includes
+#include "nsDOMWorkerThread.h"
+
+/**
+ * This class takes a list of script URLs, downloads the scripts, compiles the
+ * scripts, and then finally executes them. Due to platform limitations all
+ * network operations must happen on the main thread so this object sends events
+ * back and forth from the worker thread to the main thread. The flow goes like
+ * this:
+ *
+ * 1. (Worker thread) nsDOMWorkerScriptLoader created.
+ * 2. (Worker thread) LoadScript(s) called. Some simple argument validation is
+ * performed (currently limited to ensuring that all
+ * arguments are strings). nsDOMWorkerScriptLoader is then
+ * dispatched to the main thread.
+ * 3. (Main thread) Arguments validated as URIs, security checks performed,
+ * content policy consulted. Network loads begin.
+ * 4. (Necko thread) Necko stuff!
+ * 5. (Main thread) Completed downloads are packaged in a ScriptCompiler
+ * runnable and sent to the worker thread.
+ * 6. (Worker thread) ScriptCompiler runnables are processed (i.e. their
+ * scripts are compiled) in the order in which the necko
+ * downloads completed.
+ * 7. (Worker thread) After all loads complete and all compilation succeeds
+ * the scripts are executed in the order that the URLs were
+ * given to LoadScript(s).
+ *
+ * Currently if *anything* after 2 fails then we cancel any pending loads and
+ * bail out entirely.
+ */
+class nsDOMWorkerScriptLoader : public nsRunnable,
+ public nsIStreamLoaderObserver
+{
+ friend class AutoSuspendWorkerEvents;
+ friend class nsDOMWorkerFunctions;
+ friend class nsDOMWorkerThread;
+ friend class ScriptLoaderRunnable;
+
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSISTREAMLOADEROBSERVER
+
+ nsDOMWorkerScriptLoader();
+
+ nsresult LoadScripts(nsDOMWorkerThread* aWorker,
+ JSContext* aCx,
+ const nsTArray<nsString>& aURLs);
+
+ nsresult LoadScript(nsDOMWorkerThread* aWorker,
+ JSContext* aCx,
+ const nsString& aURL);
+
+ void Cancel();
+
+private:
+ ~nsDOMWorkerScriptLoader();
+
+ nsresult DoRunLoop();
+ nsresult VerifyScripts();
+ nsresult ExecuteScripts();
+
+ nsresult RunInternal();
+
+ nsresult OnStreamCompleteInternal(nsIStreamLoader* aLoader,
+ nsISupports* aContext,
+ nsresult aStatus,
+ PRUint32 aStringLen,
+ const PRUint8* aString);
+
+ void NotifyDone();
+
+ void SuspendWorkerEvents();
+ void ResumeWorkerEvents();
+
+ PRLock* Lock() {
+ return mWorker->Lock();
+ }
+
+ class ScriptLoaderRunnable : public nsRunnable
+ {
+ protected:
+ // Meant to be inherited.
+ ScriptLoaderRunnable(nsDOMWorkerScriptLoader* aLoader);
+ virtual ~ScriptLoaderRunnable();
+
+ public:
+ void Revoke();
+
+ protected:
+ PRBool mRevoked;
+
+ private:
+ nsDOMWorkerScriptLoader* mLoader;
+ };
+
+ class ScriptCompiler : public ScriptLoaderRunnable
+ {
+ public:
+ NS_DECL_NSIRUNNABLE
+
+ ScriptCompiler(nsDOMWorkerScriptLoader* aLoader,
+ JSContext* aCx,
+ const nsString& aScriptText,
+ const nsCString& aFilename,
+ nsAutoJSObjectHolder& aScriptObj);
+
+ private:
+ JSContext* mCx;
+ nsString mScriptText;
+ nsCString mFilename;
+ nsAutoJSObjectHolder& mScriptObj;
+ };
+
+ class ScriptLoaderDone : public ScriptLoaderRunnable
+ {
+ public:
+ NS_DECL_NSIRUNNABLE
+
+ ScriptLoaderDone(nsDOMWorkerScriptLoader* aLoader,
+ volatile PRBool* aDoneFlag);
+
+ private:
+ volatile PRBool* mDoneFlag;
+ };
+
+ class AutoSuspendWorkerEvents
+ {
+ public:
+ AutoSuspendWorkerEvents(nsDOMWorkerScriptLoader* aLoader);
+ ~AutoSuspendWorkerEvents();
+
+ private:
+ nsDOMWorkerScriptLoader* mLoader;
+ };
+
+ struct ScriptLoadInfo
+ {
+ ScriptLoadInfo() : done(PR_FALSE), result(NS_ERROR_NOT_INITIALIZED) { }
+
+ nsString url;
+ nsString scriptText;
+ PRBool done;
+ nsresult result;
+ nsCOMPtr<nsIURI> finalURI;
+ nsCOMPtr<nsIChannel> channel;
+ nsAutoJSObjectHolder scriptObj;
+ };
+
+ nsDOMWorkerThread* mWorker;
+ nsIThread* mTarget;
+ JSContext* mCx;
+
+ nsRefPtr<ScriptLoaderDone> mDoneRunnable;
+
+ PRUint32 mScriptCount;
+ nsTArray<ScriptLoadInfo> mLoadInfos;
+
+ PRPackedBool mCanceled;
+ PRPackedBool mTrackedByWorker;
+
+ // Protected by mWorker's lock!
+ nsTArray<ScriptLoaderRunnable*> mPendingRunnables;
+};
+
+#endif /* __NSDOMWORKERSCRIPTLOADER_H__ */
--- a/dom/src/threads/nsDOMWorkerSecurityManager.cpp
+++ b/dom/src/threads/nsDOMWorkerSecurityManager.cpp
@@ -83,8 +83,18 @@ nsDOMWorkerSecurityManager::CanAccess(PR
JSObject* aJSObject,
nsISupports* aObj,
nsIClassInfo* aClassInfo,
jsval aName,
void** aPolicy)
{
return NS_OK;
}
+
+JSBool
+nsDOMWorkerSecurityManager::JSCheckAccess(JSContext *cx,
+ JSObject *obj,
+ jsval id,
+ JSAccessMode mode,
+ jsval *vp)
+{
+ return JS_TRUE;
+}
--- a/dom/src/threads/nsDOMWorkerSecurityManager.h
+++ b/dom/src/threads/nsDOMWorkerSecurityManager.h
@@ -35,17 +35,22 @@
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef __NSDOMWORKERSECURITYMANAGER_H__
#define __NSDOMWORKERSECURITYMANAGER_H__
#include "nsIXPCSecurityManager.h"
+#include "jsapi.h"
class nsDOMWorkerSecurityManager : public nsIXPCSecurityManager
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIXPCSECURITYMANAGER
+
+ static JSBool JSCheckAccess(JSContext *cx, JSObject *obj, jsval id,
+ JSAccessMode mode, jsval *vp);
+
};
#endif /* __NSDOMWORKERSECURITYMANAGER_H__ */
--- a/dom/src/threads/nsDOMWorkerThread.cpp
+++ b/dom/src/threads/nsDOMWorkerThread.cpp
@@ -49,19 +49,21 @@
// Other includes
#ifdef MOZ_SHARK
#include "jsdbgapi.h"
#endif
#include "nsAutoLock.h"
#include "nsContentUtils.h"
#include "nsJSUtils.h"
#include "nsJSEnvironment.h"
+#include "nsThreadUtils.h"
// DOMWorker includes
#include "nsDOMWorkerPool.h"
+#include "nsDOMWorkerScriptLoader.h"
#include "nsDOMThreadService.h"
#include "nsDOMWorkerTimeout.h"
#define LOG(_args) PR_LOG(gDOMThreadsLog, PR_LOG_DEBUG, _args)
// XXX Could make these functions of nsDOMWorkerThread instead.
class nsDOMWorkerFunctions
{
@@ -89,16 +91,19 @@ public:
jsval* aArgv, jsval* aRval) {
return MakeTimeout(aCx, aObj, aArgc, aArgv, aRval, PR_TRUE);
}
// Used for both clearTimeout() and clearInterval().
static JSBool KillTimeout(JSContext* aCx, JSObject* aObj, uintN aArgc,
jsval* aArgv, jsval* aRval);
+ static JSBool LoadScripts(JSContext* aCx, JSObject* aObj, uintN aArgc,
+ jsval* aArgv, jsval* aRval);
+
private:
// Internal helper for SetTimeout and SetInterval.
static JSBool MakeTimeout(JSContext* aCx, JSObject* aObj, uintN aArgc,
jsval* aArgv, jsval* aRval, PRBool aIsInterval);
};
JSBool
nsDOMWorkerFunctions::Dump(JSContext* aCx,
@@ -154,17 +159,21 @@ nsDOMWorkerFunctions::PostMessage(JSCont
JSString* str;
if (aArgc && (str = JS_ValueToString(aCx, aArgv[0])) && str) {
rv = pool->PostMessageInternal(nsDependentJSString(str), worker);
}
else {
rv = pool->PostMessageInternal(EmptyString(), worker);
}
- NS_ENSURE_SUCCESS(rv, JS_FALSE);
+
+ if (NS_FAILED(rv)) {
+ JS_ReportError(aCx, "Failed to post message!");
+ return JS_FALSE;
+ }
return JS_TRUE;
}
JSBool
nsDOMWorkerFunctions::MakeTimeout(JSContext* aCx,
JSObject* /* aObj */,
uintN aArgc,
@@ -179,20 +188,26 @@ nsDOMWorkerFunctions::MakeTimeout(JSCont
if (worker->IsCanceled()) {
return JS_FALSE;
}
PRUint32 id = ++worker->mNextTimeoutId;
nsAutoPtr<nsDOMWorkerTimeout>
timeout(new nsDOMWorkerTimeout(worker, id));
- NS_ENSURE_TRUE(timeout, JS_FALSE);
+ if (!timeout) {
+ JS_ReportOutOfMemory(aCx);
+ return JS_FALSE;
+ }
nsresult rv = timeout->Init(aCx, aArgc, aArgv, aIsInterval);
- NS_ENSURE_SUCCESS(rv, JS_FALSE);
+ if (NS_FAILED(rv)) {
+ JS_ReportError(aCx, "Failed to initialize timeout!");
+ return JS_FALSE;
+ }
timeout.forget();
*aRval = INT_TO_JSVAL(id);
return JS_TRUE;
}
JSBool
@@ -221,24 +236,86 @@ nsDOMWorkerFunctions::KillTimeout(JSCont
JS_ReportError(aCx, "First argument must be a timeout id");
return JS_FALSE;
}
worker->CancelTimeout(PRUint32(id));
return JS_TRUE;
}
+JSBool JS_DLL_CALLBACK
+nsDOMWorkerFunctions::LoadScripts(JSContext* aCx,
+ JSObject* /* aObj */,
+ uintN aArgc,
+ jsval* aArgv,
+ jsval* /* aRval */)
+{
+ nsDOMWorkerThread* worker =
+ static_cast<nsDOMWorkerThread*>(JS_GetContextPrivate(aCx));
+ NS_ASSERTION(worker, "This should be set by the DOM thread service!");
+
+ if (worker->IsCanceled()) {
+ return JS_FALSE;
+ }
+
+ if (!aArgc) {
+ JS_ReportError(aCx, "Function must have at least one argument!");
+ return JS_FALSE;
+ }
+
+ nsAutoTArray<nsString, 5> urls;
+
+ if (!urls.SetCapacity((PRUint32)aArgc)) {
+ JS_ReportOutOfMemory(aCx);
+ return JS_FALSE;
+ }
+
+ for (uintN index = 0; index < aArgc; index++) {
+ jsval val = aArgv[index];
+
+ if (!JSVAL_IS_STRING(val)) {
+ JS_ReportError(aCx, "Argument %d must be a string", index);
+ return JS_FALSE;
+ }
+
+ JSString* str = JS_ValueToString(aCx, val);
+ if (!str) {
+ JS_ReportError(aCx, "Couldn't convert argument %d to a string", index);
+ return JS_FALSE;
+ }
+
+ nsString* newURL = urls.AppendElement();
+ NS_ASSERTION(newURL, "Shouldn't fail if SetCapacity succeeded above!");
+
+ newURL->Assign(nsDependentJSString(str));
+ }
+
+ nsRefPtr<nsDOMWorkerScriptLoader> loader = new nsDOMWorkerScriptLoader();
+ if (!loader) {
+ JS_ReportOutOfMemory(aCx);
+ return JS_FALSE;
+ }
+
+ nsresult rv = loader->LoadScripts(worker, aCx, urls);
+ if (NS_FAILED(rv)) {
+ return JS_FALSE;
+ }
+
+ return JS_TRUE;
+}
+
JSFunctionSpec gDOMWorkerFunctions[] = {
{ "dump", nsDOMWorkerFunctions::Dump, 1, 0, 0 },
{ "debug", nsDOMWorkerFunctions::DebugDump, 1, 0, 0 },
{ "postMessageToPool", nsDOMWorkerFunctions::PostMessage, 1, 0, 0 },
{ "setTimeout", nsDOMWorkerFunctions::SetTimeout, 1, 0, 0 },
{ "clearTimeout", nsDOMWorkerFunctions::KillTimeout, 1, 0, 0 },
{ "setInterval", nsDOMWorkerFunctions::SetInterval, 1, 0, 0 },
{ "clearInterval", nsDOMWorkerFunctions::KillTimeout, 1, 0, 0 },
+ { "loadScripts", nsDOMWorkerFunctions::LoadScripts, 1, 0, 0 },
#ifdef MOZ_SHARK
{ "startShark", js_StartShark, 0, 0, 0 },
{ "stopShark", js_StopShark, 0, 0, 0 },
{ "connectShark", js_ConnectShark, 0, 0, 0 },
{ "disconnectShark", js_DisconnectShark, 0, 0, 0 },
#endif
{ nsnull, nsnull, 0, 0, 0 }
};
@@ -301,53 +378,49 @@ nsDOMWorkerThreadContext::GetThisThread(
NS_ENSURE_TRUE(mWeakRef, NS_ERROR_OUT_OF_MEMORY);
}
NS_ADDREF(*aThisThread = mWeakRef);
return NS_OK;
}
nsDOMWorkerThread::nsDOMWorkerThread(nsDOMWorkerPool* aPool,
- const nsAString& aSource)
+ const nsAString& aSource,
+ PRBool aSourceIsURL)
: mPool(aPool),
- mSource(aSource),
- mGlobal(nsnull),
mCompiled(PR_FALSE),
mCallbackCount(0),
mNextTimeoutId(0),
mLock(nsnull)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
- NS_ASSERTION(!aSource.IsEmpty(), "Empty source string!");
+
+ if (aSourceIsURL) {
+ mSourceURL.Assign(aSource);
+ NS_ASSERTION(!mSourceURL.IsEmpty(), "Empty source url!");
+ }
+ else {
+ mSource.Assign(aSource);
+ NS_ASSERTION(!mSource.IsEmpty(), "Empty source string!");
+ }
PR_INIT_CLIST(&mTimeouts);
}
nsDOMWorkerThread::~nsDOMWorkerThread()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
if (!IsCanceled()) {
nsRefPtr<nsDOMWorkerPool> pool = Pool();
pool->NoteDyingWorker(this);
}
ClearTimeouts();
- // Only clean up if we created a global object
- if (mGlobal) {
- JSRuntime* rt;
- if (NS_SUCCEEDED(nsDOMThreadService::JSRuntimeService()->GetRuntime(&rt))) {
- JS_RemoveRootRT(rt, &mGlobal);
- }
- else {
- NS_ERROR("This shouldn't fail!");
- }
- }
-
if (mLock) {
nsAutoLock::DestroyLock(mLock);
}
}
NS_IMPL_THREADSAFE_ISUPPORTS2(nsDOMWorkerThread, nsIDOMWorkerThread,
nsIClassInfo)
NS_IMPL_CI_INTERFACE_GETTER1(nsDOMWorkerThread, nsIDOMWorkerThread)
@@ -357,23 +430,30 @@ NS_IMPL_THREADSAFE_DOM_CI(nsDOMWorkerThr
nsresult
nsDOMWorkerThread::Init()
{
mLock = nsAutoLock::NewLock("nsDOMWorkerThread::mLock");
NS_ENSURE_TRUE(mLock, NS_ERROR_OUT_OF_MEMORY);
NS_ASSERTION(!mGlobal, "Already got a global?!");
+ JSRuntime* rt;
+ nsresult rv = nsDOMThreadService::JSRuntimeService()->GetRuntime(&rt);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PRBool success = mGlobal.Hold(rt);
+ NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
+
// This is pretty cool - all we have to do to get our script executed is to
// pass a no-op runnable to the thread service and it will make sure we have
// a context and global object.
nsCOMPtr<nsIRunnable> runnable(new nsRunnable());
NS_ENSURE_TRUE(runnable, NS_ERROR_OUT_OF_MEMORY);
- nsresult rv = nsDOMThreadService::get()->Dispatch(this, runnable);
+ rv = nsDOMThreadService::get()->Dispatch(this, runnable);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
// From nsDOMWorkerBase
nsresult
nsDOMWorkerThread::HandleMessage(const nsAString& aMessage,
@@ -440,25 +520,21 @@ nsDOMWorkerThread::HandleMessage(const n
JSObject* listener;
rv = wrappedListener->GetJSObject(&listener);
NS_ENSURE_SUCCESS(rv, rv);
// And call it.
jsval rval;
PRBool success = JS_CallFunctionValue(cx, mGlobal, OBJECT_TO_JSVAL(listener),
2, argv, &rval);
- if (!success) {
+ if (!success && JS_IsExceptionPending(cx)) {
// Make sure any pending exceptions are converted to errors for the pool.
JS_ReportPendingException(cx);
}
- // We shouldn't leave any pending exceptions - our error reporter should
- // clear any exception it reports.
- NS_ASSERTION(!JS_IsExceptionPending(cx), "Huh?!");
-
return NS_OK;
}
// From nsDOMWorkerBase
nsresult
nsDOMWorkerThread::DispatchMessage(nsIRunnable* aRunnable)
{
nsresult rv = nsDOMThreadService::get()->Dispatch(this, aRunnable);
@@ -467,16 +543,19 @@ nsDOMWorkerThread::DispatchMessage(nsIRu
return NS_OK;
}
void
nsDOMWorkerThread::Cancel()
{
nsDOMWorkerBase::Cancel();
+ // Do this before waiting on the thread service below!
+ CancelScriptLoaders();
+
// If we're suspended there's a good chance that we're already paused waiting
// on the pool's monitor. Waiting on the thread service's lock will deadlock.
if (!IsSuspended()) {
nsDOMThreadService::get()->WaitForCanceledWorker(this);
}
ClearTimeouts();
}
@@ -494,17 +573,19 @@ nsDOMWorkerThread::Resume()
nsDOMWorkerBase::Resume();
ResumeTimeouts();
}
PRBool
nsDOMWorkerThread::SetGlobalForContext(JSContext* aCx)
{
PRBool success = CompileGlobalObject(aCx);
- NS_ENSURE_TRUE(success, PR_FALSE);
+ if (!success) {
+ return PR_FALSE;
+ }
JS_SetGlobalObject(aCx, mGlobal);
return PR_TRUE;
}
PRBool
nsDOMWorkerThread::CompileGlobalObject(JSContext* aCx)
{
@@ -558,42 +639,48 @@ nsDOMWorkerThread::CompileGlobalObject(J
NS_ENSURE_SUCCESS(rv, PR_FALSE);
// Set up a name for our worker object
success = JS_DefineProperty(aCx, global, "threadContext",
OBJECT_TO_JSVAL(contextObj), nsnull, nsnull,
JSPROP_ENUMERATE);
NS_ENSURE_TRUE(success, PR_FALSE);
- JSScript* script = JS_CompileUCScript(aCx, global,
- reinterpret_cast<const jschar*>
- (mSource.BeginReading()),
- mSource.Length(), nsnull, 1);
- NS_ENSURE_TRUE(script, PR_FALSE);
+ jsval val;
- JSRuntime* rt;
- rv = nsDOMThreadService::JSRuntimeService()->GetRuntime(&rt);
- NS_ENSURE_SUCCESS(rv, PR_FALSE);
-
+ // From here on out we have to remember to null mGlobal if something fails!
mGlobal = global;
- success = JS_AddNamedRootRT(rt, &mGlobal, "nsDOMWorkerThread Global Object");
- if (!success) {
- NS_WARNING("Failed to root global object for worker thread!");
- mGlobal = nsnull;
- return PR_FALSE;
- }
+
+ if (mSource.IsEmpty()) {
+ NS_ASSERTION(!mSourceURL.IsEmpty(), "Must have a url here!");
+
+ nsRefPtr<nsDOMWorkerScriptLoader> loader = new nsDOMWorkerScriptLoader();
+ NS_ASSERTION(loader, "Out of memory!");
+ if (!loader) {
+ mGlobal = NULL;
+ return PR_FALSE;
+ }
- // Execute the script
- jsval val;
- success = JS_ExecuteScript(aCx, global, script, &val);
- if (!success) {
- NS_WARNING("Failed to evaluate script for worker thread!");
- JS_RemoveRootRT(rt, &mGlobal);
- mGlobal = nsnull;
- return PR_FALSE;
+ rv = loader->LoadScript(this, aCx, mSourceURL);
+ JS_ReportPendingException(aCx);
+ if (NS_FAILED(rv)) {
+ mGlobal = NULL;
+ return PR_FALSE;
+ }
+ }
+ else {
+ NS_ASSERTION(!mSource.IsEmpty(), "No source text!");
+
+ // Evaluate and execute the script
+ success = JS_EvaluateUCScript(aCx, global, mSource.get(), mSource.Length(),
+ "DOMWorker inline script", 1, &val);
+ if (!success) {
+ mGlobal = NULL;
+ return PR_FALSE;
+ }
}
// See if the message listener function was defined.
nsCOMPtr<nsIDOMWorkerMessageListener> listener;
if (JS_LookupProperty(aCx, global, "messageListener", &val) &&
JSVAL_IS_OBJECT(val) &&
NS_SUCCEEDED(xpc->WrapJS(aCx, JSVAL_TO_OBJECT(val),
NS_GET_IID(nsIDOMWorkerMessageListener),
@@ -740,15 +827,35 @@ nsDOMWorkerThread::ResumeTimeouts()
PRTime now = PR_Now();
PRUint32 count = timeouts.Length();
for (PRUint32 i = 0; i < count; i++) {
timeouts[i]->Resume(now);
}
}
+void
+nsDOMWorkerThread::CancelScriptLoaders()
+{
+ nsAutoTArray<nsDOMWorkerScriptLoader*, 20> loaders;
+
+ // Must call cancel on the loaders outside the lock!
+ {
+ nsAutoLock lock(mLock);
+ loaders.AppendElements(mScriptLoaders);
+
+ // Don't clear mScriptLoaders, they'll remove themselves as they get
+ // destroyed.
+ }
+
+ PRUint32 loaderCount = loaders.Length();
+ for (PRUint32 index = 0; index < loaderCount; index++) {
+ loaders[index]->Cancel();
+ }
+}
+
NS_IMETHODIMP
nsDOMWorkerThread::PostMessage(const nsAString& aMessage)
{
nsresult rv = PostMessageInternal(aMessage);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
--- a/dom/src/threads/nsDOMWorkerThread.h
+++ b/dom/src/threads/nsDOMWorkerThread.h
@@ -42,18 +42,20 @@
// Bases
#include "nsDOMWorkerBase.h"
#include "nsIClassInfo.h"
#include "nsIDOMThreads.h"
// Other includes
#include "jsapi.h"
+#include "nsAutoJSObjectHolder.h"
#include "nsCOMPtr.h"
#include "nsStringGlue.h"
+#include "nsTArray.h"
#include "nsThreadUtils.h"
#include "prclist.h"
#include "prlock.h"
// DOMWorker includes
#include "nsDOMThreadService.h"
// Macro to generate nsIClassInfo methods for these threadsafe DOM classes
@@ -118,27 +120,29 @@ class nsDOMWorkerTimeout;
class nsDOMWorkerThread : public nsDOMWorkerBase,
public nsIDOMWorkerThread,
public nsIClassInfo
{
friend class nsDOMCreateJSContextRunnable;
friend class nsDOMWorkerFunctions;
friend class nsDOMWorkerPool;
friend class nsDOMWorkerRunnable;
+ friend class nsDOMWorkerScriptLoader;
friend class nsDOMWorkerTimeout;
friend JSBool DOMWorkerOperationCallback(JSContext* aCx);
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIDOMWORKERTHREAD
NS_DECL_NSICLASSINFO
nsDOMWorkerThread(nsDOMWorkerPool* aPool,
- const nsAString& aSource);
+ const nsAString& aSource,
+ PRBool aSourceIsURL);
virtual nsDOMWorkerPool* Pool() {
NS_ASSERTION(!IsCanceled(), "Don't touch Pool after we've been canceled!");
return mPool;
}
private:
virtual ~nsDOMWorkerThread();
@@ -164,23 +168,32 @@ private:
void AddTimeout(nsDOMWorkerTimeout* aTimeout);
void RemoveTimeout(nsDOMWorkerTimeout* aTimeout);
void ClearTimeouts();
void CancelTimeout(PRUint32 aId);
void SuspendTimeouts();
void ResumeTimeouts();
+ void CancelScriptLoaders();
+
+ PRLock* Lock() {
+ return mLock;
+ }
+
nsDOMWorkerPool* mPool;
nsString mSource;
+ nsString mSourceURL;
- JSObject* mGlobal;
+ nsAutoJSObjectHolder mGlobal;
PRBool mCompiled;
PRUint32 mCallbackCount;
PRUint32 mNextTimeoutId;
PRLock* mLock;
PRCList mTimeouts;
+
+ nsTArray<nsDOMWorkerScriptLoader*> mScriptLoaders;
};
#endif /* __NSDOMWORKERTHREAD_H__ */
--- a/dom/src/threads/test/Makefile.in
+++ b/dom/src/threads/test/Makefile.in
@@ -42,16 +42,22 @@ srcdir = @srcdir@
VPATH = @srcdir@
relativesrcdir = dom/src/threads/tests
include $(DEPTH)/config/autoconf.mk
include $(topsrcdir)/config/rules.mk
_TEST_FILES = \
+ importScripts_worker.js \
+ importScripts_worker_imported1.js \
+ importScripts_worker_imported2.js \
+ importScripts_worker_imported3.js \
+ importScripts_worker_imported4.js \
+ test_importScripts.html \
test_simpleThread.html \
test_threadErrors.html \
test_threadTimeouts.html \
test_longThread.html \
$(NULL)
libs:: $(_TEST_FILES)
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/importScripts_worker.js
@@ -0,0 +1,54 @@
+function messageListener(message, source) {
+ switch (message) {
+ case 'start':
+ loadScripts("importScripts_worker_imported2.js");
+ importedScriptFunction2();
+ tryBadScripts();
+ source.postMessage('started');
+ break;
+ case 'stop':
+ tryBadScripts();
+ postMessageToPool('stopped');
+ break;
+ default:
+ throw new Error("Bad message: " + message);
+ break;
+ }
+}
+
+// This caused security exceptions in the past, make sure it doesn't!
+var constructor = {}.constructor;
+
+loadScripts("importScripts_worker_imported1.js");
+
+// Try to call a function defined in the imported script.
+importedScriptFunction();
+
+function tryBadScripts() {
+ var badScripts = [
+ // Has a syntax error
+ "importScripts_worker_imported3.js",
+ // Throws an exception
+ "importScripts_worker_imported4.js",
+ // Shouldn't exist!
+ "http://flippety.com/floppety/foo.js",
+ // Not a valid url
+ "http://flippety::foo_js ftw"
+ ];
+
+ for (var i = 0; i < badScripts.length; i++) {
+ var caughtException = false;
+ var url = badScripts[i];
+ try {
+ loadScripts(url);
+ }
+ catch (e) {
+ caughtException = true;
+ }
+ if (!caughtException) {
+ throw "Bad script didn't throw exception: " + url;
+ }
+ }
+}
+
+tryBadScripts();
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/importScripts_worker_imported1.js
@@ -0,0 +1,7 @@
+// This caused security exceptions in the past, make sure it doesn't!
+var myConstructor = {}.constructor;
+
+// Try to call a function defined in the imported script.
+function importedScriptFunction() {
+ dump("running importedScriptFunction\n");
+}
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/importScripts_worker_imported2.js
@@ -0,0 +1,7 @@
+// This caused security exceptions in the past, make sure it doesn't!
+var myConstructor2 = {}.constructor;
+
+// Try to call a function defined in the imported script.
+function importedScriptFunction2() {
+ dump("running importedScriptFunction2\n");
+}
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/importScripts_worker_imported3.js
@@ -0,0 +1,2 @@
+// Deliberate syntax error, should generate a worker exception!
+for (var index = 0; index < 100) {}
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/importScripts_worker_imported4.js
@@ -0,0 +1,2 @@
+// Deliberate throw, should generate a worker exception!
+throw new Error("Bah!");
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/test_importScripts.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Threads (Bug 437152)
+-->
+<head>
+ <title>Test for DOM Worker Threads (Bug 437152)</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=437152">DOM Worker Threads Bug 437152</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ var pool = navigator.newWorkerPool();
+ pool.messageListener = function(message, source) {
+ switch (message) {
+ case "started":
+ source.postMessage("stop");
+ break;
+ case "stopped":
+ SimpleTest.finish();
+ break;
+ default:
+ ok(false, "Unexpected message:" + message);
+ SimpleTest.finish();
+ }
+ };
+
+ pool.errorListener = function(error, source) {
+ ok(false, "Worker had an error:" + error);
+ SimpleTest.finish();
+ }
+
+ var worker = pool.createWorkerFromURL("importScripts_worker.js");
+ worker.postMessage("start");
+
+ SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
+