diff --git a/dom/Makefile.in b/dom/Makefile.in index 2798eb318404..8b9f73976eae 100644 --- a/dom/Makefile.in +++ b/dom/Makefile.in @@ -62,6 +62,7 @@ DIRS = \ interfaces/json \ interfaces/offline \ interfaces/geolocation \ + interfaces/threads \ interfaces/notification \ interfaces/svg \ $(NULL) @@ -80,7 +81,6 @@ DIRS += \ indexedDB \ system \ ipc \ - workers \ $(NULL) ifdef ENABLE_TESTS diff --git a/dom/base/nsDOMClassInfo.cpp b/dom/base/nsDOMClassInfo.cpp index 9d6c64308fa4..db49f9300a78 100644 --- a/dom/base/nsDOMClassInfo.cpp +++ b/dom/base/nsDOMClassInfo.cpp @@ -460,7 +460,7 @@ #include "nsIDOMGeoPositionError.h" // Workers -#include "mozilla/dom/workers/Workers.h" +#include "nsDOMWorker.h" #include "nsDOMFile.h" #include "nsDOMFileReader.h" @@ -599,6 +599,9 @@ DOMCI_DATA(ContentFrameMessageManager, void) DOMCI_DATA(DOMPrototype, void) DOMCI_DATA(DOMConstructor, void) +DOMCI_DATA(Worker, void) +DOMCI_DATA(ChromeWorker, void) + #define NS_DEFINE_CLASSINFO_DATA_WITH_NAME(_class, _name, _helper, \ _flags) \ { #_name, \ @@ -1428,6 +1431,11 @@ static nsDOMClassInfoData sClassInfoData[] = { NS_DEFINE_CLASSINFO_DATA_WITH_NAME(MathMLElement, Element, nsElementSH, ELEMENT_SCRIPTABLE_FLAGS) + NS_DEFINE_CLASSINFO_DATA(Worker, nsDOMGenericSH, + DOM_DEFAULT_SCRIPTABLE_FLAGS) + NS_DEFINE_CHROME_ONLY_CLASSINFO_DATA(ChromeWorker, nsDOMGenericSH, + DOM_DEFAULT_SCRIPTABLE_FLAGS) + NS_DEFINE_CLASSINFO_DATA(WebGLRenderingContext, nsWebGLViewportHandlerSH, DOM_DEFAULT_SCRIPTABLE_FLAGS) NS_DEFINE_CLASSINFO_DATA(WebGLBuffer, nsDOMGenericSH, @@ -1564,6 +1572,8 @@ struct nsConstructorFuncMapData static const nsConstructorFuncMapData kConstructorFuncMap[] = { + NS_DEFINE_CONSTRUCTOR_FUNC_DATA(Worker, nsDOMWorker::NewWorker) + NS_DEFINE_CONSTRUCTOR_FUNC_DATA(ChromeWorker, nsDOMWorker::NewChromeWorker) NS_DEFINE_CONSTRUCTOR_FUNC_DATA(File, nsDOMFileFile::NewFile) NS_DEFINE_CONSTRUCTOR_FUNC_DATA(MozBlobBuilder, NS_NewBlobBuilder) }; @@ -4146,6 +4156,18 @@ nsDOMClassInfo::Init() DOM_CLASSINFO_MAP_ENTRY(nsIDOMNodeSelector) DOM_CLASSINFO_MAP_END + DOM_CLASSINFO_MAP_BEGIN(Worker, nsIWorker) + DOM_CLASSINFO_MAP_ENTRY(nsIWorker) + DOM_CLASSINFO_MAP_ENTRY(nsIAbstractWorker) + DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget) + DOM_CLASSINFO_MAP_END + + DOM_CLASSINFO_MAP_BEGIN(ChromeWorker, nsIWorker) + DOM_CLASSINFO_MAP_ENTRY(nsIWorker) + DOM_CLASSINFO_MAP_ENTRY(nsIAbstractWorker) + DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget) + DOM_CLASSINFO_MAP_END + DOM_CLASSINFO_MAP_BEGIN(WebGLRenderingContext, nsIDOMWebGLRenderingContext) DOM_CLASSINFO_MAP_ENTRY(nsIDOMWebGLRenderingContext) DOM_CLASSINFO_MAP_END @@ -6056,30 +6078,8 @@ nsDOMConstructor::HasInstance(nsIXPConnectWrappedNative *wrapper, const nsGlobalNameStruct *name_struct; rv = GetNameStruct(NS_ConvertASCIItoUTF16(dom_class->name), &name_struct); - if (NS_FAILED(rv)) { - return rv; - } - if (!name_struct) { - // This isn't a normal DOM object, see if this constructor lives on its - // prototype chain. - jsval val; - if (!JS_GetProperty(cx, obj, "prototype", &val)) { - return NS_ERROR_UNEXPECTED; - } - - JS_ASSERT(!JSVAL_IS_PRIMITIVE(val)); - JSObject *dot_prototype = JSVAL_TO_OBJECT(val); - - JSObject *proto = JS_GetPrototype(cx, dom_obj); - for ( ; proto; proto = JS_GetPrototype(cx, proto)) { - if (proto == dot_prototype) { - *bp = PR_TRUE; - break; - } - } - - return NS_OK; + return rv; } if (name_struct->mType != nsGlobalNameStruct::eTypeClassConstructor && @@ -6714,10 +6714,6 @@ ContentWindowGetter(JSContext *cx, uintN argc, jsval *vp) return ::JS_GetProperty(cx, obj, "content", vp); } -static JSNewResolveOp sOtherResolveFuncs[] = { - mozilla::dom::workers::ResolveWorkerClasses -}; - NS_IMETHODIMP nsWindowSH::NewResolve(nsIXPConnectWrappedNative *wrapper, JSContext *cx, JSObject *obj, jsid id, PRUint32 flags, @@ -6944,16 +6940,6 @@ nsWindowSH::NewResolve(nsIXPConnectWrappedNative *wrapper, JSContext *cx, if (!(flags & JSRESOLVE_ASSIGNING)) { JSAutoRequest ar(cx); - // Resolve special classes. - for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(sOtherResolveFuncs); i++) { - if (!sOtherResolveFuncs[i](cx, obj, id, flags, objp)) { - return NS_ERROR_FAILURE; - } - if (*objp) { - return NS_OK; - } - } - // Call GlobalResolve() after we call FindChildWithName() so // that named child frames will override external properties // which have been registered with the script namespace manager. diff --git a/dom/base/nsDOMClassInfoClasses.h b/dom/base/nsDOMClassInfoClasses.h index 77822a21566d..3e0513ff9eb2 100644 --- a/dom/base/nsDOMClassInfoClasses.h +++ b/dom/base/nsDOMClassInfoClasses.h @@ -462,6 +462,9 @@ DOMCI_CLASS(MozTouchEvent) DOMCI_CLASS(MathMLElement) +DOMCI_CLASS(Worker) +DOMCI_CLASS(ChromeWorker) + // WebGL DOMCI_CLASS(WebGLRenderingContext) DOMCI_CLASS(WebGLBuffer) diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 88b546d5a655..b29bd0bf1a09 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -91,9 +91,9 @@ #include "nsLayoutStatics.h" #include "nsCycleCollector.h" #include "nsCCUncollectableMarker.h" +#include "nsDOMThreadService.h" #include "nsAutoJSValHolder.h" #include "nsDOMMediaQueryList.h" -#include "mozilla/dom/workers/Workers.h" // Interfaces Needed #include "nsIFrame.h" @@ -1244,11 +1244,18 @@ nsGlobalWindow::FreeInnerObjects(PRBool aClearScope) NS_ASSERTION(IsInnerWindow(), "Don't free inner objects on an outer window"); // Kill all of the workers for this window. - nsIScriptContext *scx = GetContextInternal(); - JSContext *cx = scx ? - static_cast(scx->GetNativeContext()) : - nsnull; - mozilla::dom::workers::CancelWorkersForWindow(cx, this); + nsDOMThreadService* dts = nsDOMThreadService::get(); + if (dts) { + nsIScriptContext *scx = GetContextInternal(); + + JSContext *cx = scx ? (JSContext *)scx->GetNativeContext() : nsnull; + + // Have to suspend this request here because CancelWorkersForGlobal will + // lock until the worker has died and that could cause a deadlock. + JSAutoSuspendRequest asr(cx); + + dts->CancelWorkersForGlobal(static_cast(this)); + } // Close all IndexedDB databases for this window. indexedDB::IndexedDatabaseManager* idbManager = @@ -9936,13 +9943,11 @@ nsGlobalWindow::SuspendTimeouts(PRUint32 aIncrease, if (!suspended) { DisableDeviceMotionUpdates(); - // Suspend all of the workers for this window. - nsIScriptContext *scx = GetContextInternal(); - JSContext *cx = scx ? - static_cast(scx->GetNativeContext()) : - nsnull; - mozilla::dom::workers::SuspendWorkersForWindow(cx, this); - + nsDOMThreadService* dts = nsDOMThreadService::get(); + if (dts) { + dts->SuspendWorkersForGlobal(static_cast(this)); + } + TimeStamp now = TimeStamp::Now(); for (nsTimeout *t = FirstTimeout(); IsTimeout(t); t = t->Next()) { // Set mTimeRemaining to be the time remaining for this timer. @@ -10014,12 +10019,10 @@ nsGlobalWindow::ResumeTimeouts(PRBool aThawChildren) if (shouldResume) { EnableDeviceMotionUpdates(); - // Resume all of the workers for this window. - nsIScriptContext *scx = GetContextInternal(); - JSContext *cx = scx ? - static_cast(scx->GetNativeContext()) : - nsnull; - mozilla::dom::workers::ResumeWorkersForWindow(cx, this); + nsDOMThreadService* dts = nsDOMThreadService::get(); + if (dts) { + dts->ResumeWorkersForGlobal(static_cast(this)); + } // Restore all of the timeouts, using the stored time remaining // (stored in timeout->mTimeRemaining). diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index 66a51cd50ddf..909154db6ebf 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -3691,32 +3691,32 @@ ObjectPrincipalFinder(JSContext *cx, JSObject *obj) return jsPrincipals; } -JSObject* -NS_DOMReadStructuredClone(JSContext* cx, - JSStructuredCloneReader* reader, - uint32 tag, - uint32 data, - void* closure) +static JSObject* +DOMReadStructuredClone(JSContext* cx, + JSStructuredCloneReader* reader, + uint32 tag, + uint32 data, + void* closure) { // We don't currently support any extensions to structured cloning. nsDOMClassInfo::ThrowJSException(cx, NS_ERROR_DOM_DATA_CLONE_ERR); return nsnull; } -JSBool -NS_DOMWriteStructuredClone(JSContext* cx, - JSStructuredCloneWriter* writer, - JSObject* obj, - void *closure) +static JSBool +DOMWriteStructuredClone(JSContext* cx, + JSStructuredCloneWriter* writer, + JSObject* obj, + void *closure) { // We don't currently support any extensions to structured cloning. nsDOMClassInfo::ThrowJSException(cx, NS_ERROR_DOM_DATA_CLONE_ERR); return JS_FALSE; } -void -NS_DOMStructuredCloneError(JSContext* cx, - uint32 errorid) +static void +DOMStructuredCloneError(JSContext* cx, + uint32 errorid) { // We don't currently support any extensions to structured cloning. nsDOMClassInfo::ThrowJSException(cx, NS_ERROR_DOM_DATA_CLONE_ERR); @@ -3760,9 +3760,9 @@ nsJSRuntime::Init() // Set up the structured clone callbacks. static JSStructuredCloneCallbacks cloneCallbacks = { - NS_DOMReadStructuredClone, - NS_DOMWriteStructuredClone, - NS_DOMStructuredCloneError + DOMReadStructuredClone, + DOMWriteStructuredClone, + DOMStructuredCloneError }; JS_SetStructuredCloneCallbacks(sRuntime, &cloneCallbacks); diff --git a/dom/base/nsJSEnvironment.h b/dom/base/nsJSEnvironment.h index a0e1835dcd4f..c1cedbec23c3 100644 --- a/dom/base/nsJSEnvironment.h +++ b/dom/base/nsJSEnvironment.h @@ -363,14 +363,4 @@ nsresult NS_CreateJSRuntime(nsIScriptRuntime **aRuntime); /* prototypes */ void NS_ScriptErrorReporter(JSContext *cx, const char *message, JSErrorReport *report); -JSObject* NS_DOMReadStructuredClone(JSContext* cx, - JSStructuredCloneReader* reader, uint32 tag, - uint32 data, void* closure); - -JSBool NS_DOMWriteStructuredClone(JSContext* cx, - JSStructuredCloneWriter* writer, - JSObject* obj, void *closure); - -void NS_DOMStructuredCloneError(JSContext* cx, uint32 errorid); - #endif /* nsJSEnvironment_h___ */ diff --git a/dom/dom-config.mk b/dom/dom-config.mk index 8d13a462c42e..34e5c6d9ed52 100644 --- a/dom/dom-config.mk +++ b/dom/dom-config.mk @@ -7,7 +7,7 @@ DOM_SRCDIRS = \ dom/src/offline \ dom/src/geolocation \ dom/src/notification \ - dom/workers \ + dom/src/threads \ content/xbl/src \ content/xul/document/src \ content/events/src \ diff --git a/dom/interfaces/threads/Makefile.in b/dom/interfaces/threads/Makefile.in new file mode 100644 index 000000000000..40d9107ffedd --- /dev/null +++ b/dom/interfaces/threads/Makefile.in @@ -0,0 +1,52 @@ +# +# ***** 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 mozilla.org code. +# +# The Initial Developer of the Original Code is Mozilla Foundation. +# Portions created by the Initial Developer are Copyright (C) 2007 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Vladimir Vukicevic (Original Author) +# Ben Turner +# +# Alternatively, the contents of this file may be used under the terms of +# either of 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 ***** + +DEPTH = ../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = dom +XPIDL_MODULE = dom_threads +GRE_MODULE = 1 + +XPIDLSRCS = nsIDOMWorkers.idl + +include $(topsrcdir)/config/rules.mk diff --git a/dom/interfaces/threads/nsIDOMWorkers.idl b/dom/interfaces/threads/nsIDOMWorkers.idl new file mode 100644 index 000000000000..a30f0a24b879 --- /dev/null +++ b/dom/interfaces/threads/nsIDOMWorkers.idl @@ -0,0 +1,156 @@ +/* -*- 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 Web Workers. + * + * 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 (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 ***** */ + +/** + * From http://www.whatwg.org/specs/web-workers/current-work + */ + +#include "nsIDOMEvent.idl" +#include "nsIDOMEventTarget.idl" + +interface nsIDOMEventListener; + +[scriptable, uuid(ab3725b8-3fca-40cc-a42c-92fb154ef01d)] +interface nsIWorkerMessagePort : nsISupports +{ + void postMessage(/* in JSObject aMessage */); +}; + +[scriptable, uuid(508f2d49-e9a0-4fe8-bd33-321820173b4a)] +interface nsIWorkerMessageEvent : nsIDOMEvent +{ + readonly attribute DOMString data; + readonly attribute DOMString origin; + + readonly attribute nsISupports source; + + void initMessageEvent(in DOMString aTypeArg, + in boolean aCanBubbleArg, + in boolean aCancelableArg, + in DOMString aDataArg, + in DOMString aOriginArg, + in nsISupports aSourceArg); +}; + +[scriptable, uuid(73d82c1d-05de-49c9-a23b-7121ff09a67a)] +interface nsIWorkerErrorEvent : nsIDOMEvent +{ + readonly attribute DOMString message; + readonly attribute DOMString filename; + + readonly attribute unsigned long lineno; + + void initErrorEvent(in DOMString aTypeArg, + in boolean aCanBubbleArg, + in boolean aCancelableArg, + in DOMString aMessageArg, + in DOMString aFilenameArg, + in unsigned long aLinenoArg); +}; + +[scriptable, uuid(17a005c3-4f2f-4bb6-b169-c181fa6873de)] +interface nsIWorkerLocation : nsISupports +{ + readonly attribute AUTF8String href; + readonly attribute AUTF8String protocol; + readonly attribute AUTF8String host; + readonly attribute AUTF8String hostname; + readonly attribute AUTF8String port; + readonly attribute AUTF8String pathname; + readonly attribute AUTF8String search; + readonly attribute AUTF8String hash; + + AUTF8String toString(); +}; + +[scriptable, uuid(74fb665a-e477-4ce2-b3c6-c58b1b28b6c3)] +interface nsIWorkerNavigator : nsISupports +{ + readonly attribute DOMString appName; + readonly attribute DOMString appVersion; + readonly attribute DOMString platform; + readonly attribute DOMString userAgent; +}; + +[scriptable, uuid(c111e7d3-8044-4458-aa7b-637696ffb841)] +interface nsIWorkerGlobalScope : nsISupports +{ + readonly attribute nsIWorkerGlobalScope self; + readonly attribute nsIWorkerNavigator navigator; + readonly attribute nsIWorkerLocation location; + + attribute nsIDOMEventListener onerror; +}; + +[scriptable, uuid(5c55ea4b-e4ac-4ceb-bfeb-46bd5e521b8a)] +interface nsIWorkerScope : nsIWorkerGlobalScope +{ + void postMessage(/* in JSObject aMessage */); + + void close(); + + attribute nsIDOMEventListener onmessage; + attribute nsIDOMEventListener onclose; +}; + +[scriptable, builtinclass, uuid(b90b7561-b5e2-4545-84b0-280dbaaa94ea)] +interface nsIAbstractWorker : nsIDOMEventTarget +{ + attribute nsIDOMEventListener onerror; +}; + +[scriptable, builtinclass, uuid(daf945c3-8d29-4724-8939-dd383f7d27a7)] +interface nsIWorker : nsIAbstractWorker +{ + void postMessage(/* in JSObject aMessage */); + + attribute nsIDOMEventListener onmessage; + + void terminate(); +}; + +[scriptable, uuid(cfc4bb32-ca83-4d58-9b6f-66f8054a333a)] +interface nsIWorkerFactory : nsISupports +{ + nsIWorker newChromeWorker(/* in DOMString aScriptURL */); +}; + +%{ C++ +#define NS_WORKERFACTORY_CONTRACTID \ +"@mozilla.org/threads/workerfactory;1" +%} diff --git a/dom/src/Makefile.in b/dom/src/Makefile.in index 3de9f9792d56..757b6714bfb2 100644 --- a/dom/src/Makefile.in +++ b/dom/src/Makefile.in @@ -42,6 +42,6 @@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk -DIRS = jsurl events storage offline json geolocation notification +DIRS = jsurl events storage offline json geolocation threads notification include $(topsrcdir)/config/rules.mk diff --git a/dom/workers/Makefile.in b/dom/src/threads/Makefile.in similarity index 82% rename from dom/workers/Makefile.in rename to dom/src/threads/Makefile.in index 578d141cb8c6..06c1c14eaddf 100644 --- a/dom/workers/Makefile.in +++ b/dom/src/threads/Makefile.in @@ -36,7 +36,7 @@ # # ***** END LICENSE BLOCK ***** -DEPTH = ../.. +DEPTH = ../../.. topsrcdir = @top_srcdir@ srcdir = @srcdir@ VPATH = @srcdir@ @@ -44,38 +44,31 @@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk MODULE = dom -LIBRARY_NAME = domworkers_s +LIBRARY_NAME = domthreads_s LIBXUL_LIBRARY = 1 FORCE_STATIC_LIB = 1 -EXPORTS_NAMESPACES = mozilla/dom/workers - -EXPORTS_mozilla/dom/workers = Workers.h - CPPSRCS = \ - ChromeWorkerScope.cpp \ - Events.cpp \ - EventTarget.cpp \ - Exceptions.cpp \ - ListenerManager.cpp \ - Location.cpp \ - Navigator.cpp \ - Principal.cpp \ - RuntimeService.cpp \ - ScriptLoader.cpp \ - Worker.cpp \ - WorkerPrivate.cpp \ - WorkerScope.cpp \ - XMLHttpRequest.cpp \ - XMLHttpRequestPrivate.cpp \ + nsDOMThreadService.cpp \ + nsDOMWorker.cpp \ + nsDOMWorkerEvents.cpp \ + nsDOMWorkerLocation.cpp \ + nsDOMWorkerMessageHandler.cpp \ + nsDOMWorkerNavigator.cpp \ + nsDOMWorkerPool.cpp \ + nsDOMWorkerScriptLoader.cpp \ + nsDOMWorkerSecurityManager.cpp \ + nsDOMWorkerTimeout.cpp \ + nsDOMWorkerXHR.cpp \ + nsDOMWorkerXHRProxy.cpp \ $(NULL) LOCAL_INCLUDES = \ -I$(topsrcdir)/content/base/src \ -I$(topsrcdir)/content/events/src \ -I$(topsrcdir)/dom/base \ + -I$(topsrcdir)/dom/src/json \ -I$(topsrcdir)/js/src/xpconnect/src \ - -I$(topsrcdir)/xpcom/build \ $(NULL) ifdef ENABLE_TESTS diff --git a/dom/src/threads/nsDOMThreadService.cpp b/dom/src/threads/nsDOMThreadService.cpp new file mode 100644 index 000000000000..f6c7de5651c0 --- /dev/null +++ b/dom/src/threads/nsDOMThreadService.cpp @@ -0,0 +1,1608 @@ +/* -*- Mode: c++; c-basic-offset: 2; 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): + * Vladimir Vukicevic (Original Author) + * Ben Turner + * + * 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 "jscntxt.h" + +#include "nsDOMThreadService.h" + +// Interfaces +#include "nsIComponentManager.h" +#include "nsIConsoleService.h" +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsIEventTarget.h" +#include "nsIJSContextStack.h" +#include "nsIJSRuntimeService.h" +#include "nsIObserverService.h" +#include "nsIScriptError.h" +#include "nsIScriptGlobalObject.h" +#include "nsIServiceManager.h" +#include "nsISupportsPriority.h" +#include "nsIThreadPool.h" +#include "nsIXPConnect.h" +#include "nsPIDOMWindow.h" + +// Other includes +#include "nsAutoPtr.h" +#include "nsContentUtils.h" +#include "nsDeque.h" +#include "nsGlobalWindow.h" +#include "nsIClassInfoImpl.h" +#include "nsStringBuffer.h" +#include "nsThreadUtils.h" +#include "nsXPCOM.h" +#include "nsXPCOMCID.h" +#include "nsXPCOMCIDInternal.h" +#include "pratom.h" +#include "prthread.h" +#include "mozilla/Preferences.h" + +// DOMWorker includes +#include "nsDOMWorker.h" +#include "nsDOMWorkerEvents.h" +#include "nsDOMWorkerMacros.h" +#include "nsDOMWorkerMessageHandler.h" +#include "nsDOMWorkerPool.h" +#include "nsDOMWorkerSecurityManager.h" +#include "nsDOMWorkerTimeout.h" + +using namespace mozilla; + +#ifdef PR_LOGGING +PRLogModuleInfo *gDOMThreadsLog = nsnull; +#endif +#define LOG(_args) PR_LOG(gDOMThreadsLog, PR_LOG_DEBUG, _args) + +// The maximum number of threads in the internal thread pool +#define THREADPOOL_MAX_THREADS 3 + +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); + +// A "bad" value for the NSPR TLS functions. +#define BAD_TLS_INDEX (PRUintn)-1 + +// Easy access for static functions. No reference here. +static nsDOMThreadService* gDOMThreadService = nsnull; + +// These pointers actually carry references and must be released. +static nsIObserverService* gObserverService = nsnull; +static nsIJSRuntimeService* gJSRuntimeService = nsnull; +static nsIThreadJSContextStack* gThreadJSContextStack = nsnull; +static nsIXPCSecurityManager* gWorkerSecurityManager = nsnull; + +PRUintn gJSContextIndex = BAD_TLS_INDEX; + +static const char* sPrefsToWatch[] = { + "dom.max_script_run_time" +}; + +// The length of time the close handler is allowed to run in milliseconds. +static PRUint32 gWorkerCloseHandlerTimeoutMS = 10000; + +/** + * Simple class to automatically destroy a JSContext to make error handling + * easier. + */ +class JSAutoContextDestroyer +{ +public: + JSAutoContextDestroyer(JSContext* aCx) + : mCx(aCx) { } + + ~JSAutoContextDestroyer() { + if (mCx) { + nsContentUtils::XPConnect()->ReleaseJSContext(mCx, PR_TRUE); + } + } + + operator JSContext*() { + return mCx; + } + + JSContext* forget() { + JSContext* cx = mCx; + mCx = nsnull; + return cx; + } + +private: + JSContext* mCx; +}; + +class nsDestroyJSContextRunnable : public nsIRunnable +{ +public: + NS_DECL_ISUPPORTS + + nsDestroyJSContextRunnable(JSContext* aCx) + : mCx(aCx) + { + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(aCx, "Null pointer!"); + NS_ASSERTION(!JS_GetGlobalObject(aCx), "Should not have a global!"); + + // We're removing this context from this thread. Let the JS engine know. + JS_ClearContextThread(aCx); + } + + NS_IMETHOD Run() + { + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + // We're about to use this context on this thread. Let the JS engine know. + if (!!JS_SetContextThread(mCx)) { + NS_WARNING("JS_SetContextThread failed!"); + } + + if (nsContentUtils::XPConnect()) { + nsContentUtils::XPConnect()->ReleaseJSContext(mCx, PR_TRUE); + } + else { + NS_WARNING("Failed to release JSContext!"); + } + + return NS_OK; + } + +private: + JSContext* mCx; +}; + +NS_IMPL_THREADSAFE_ISUPPORTS1(nsDestroyJSContextRunnable, nsIRunnable) + +/** + * This class is used as to post an error to the worker's outer handler. + */ +class nsReportErrorRunnable : public nsIRunnable +{ +public: + NS_DECL_ISUPPORTS + + nsReportErrorRunnable(nsDOMWorker* aWorker, + nsIScriptError* aScriptError) + : mWorker(aWorker), mWorkerWN(aWorker->GetWrappedNative()), + mScriptError(aScriptError) { + NS_ASSERTION(aScriptError, "Null pointer!"); + } + + NS_IMETHOD Run() { + if (mWorker->IsCanceled()) { + return NS_OK; + } + +#ifdef DEBUG + { + nsRefPtr parent = mWorker->GetParent(); + if (NS_IsMainThread()) { + NS_ASSERTION(!parent, "Shouldn't have a parent on the main thread!"); + } + else { + NS_ASSERTION(parent, "Should have a parent!"); + + JSContext* cx = nsDOMThreadService::get()->GetCurrentContext(); + NS_ASSERTION(cx, "No context!"); + + nsDOMWorker* currentWorker = (nsDOMWorker*)JS_GetContextPrivate(cx); + NS_ASSERTION(currentWorker == parent, "Wrong worker!"); + } + } +#endif + + NS_NAMED_LITERAL_STRING(errorStr, "error"); + + nsresult rv; + + if (mWorker->HasListeners(errorStr)) { + // Construct the error event. + nsString message; + rv = mScriptError->GetErrorMessage(message); + NS_ENSURE_SUCCESS(rv, rv); + + nsString filename; + rv = mScriptError->GetSourceName(filename); + NS_ENSURE_SUCCESS(rv, rv); + + PRUint32 lineno; + rv = mScriptError->GetLineNumber(&lineno); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr event(new nsDOMWorkerErrorEvent()); + NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY); + + rv = event->InitErrorEvent(errorStr, PR_FALSE, PR_TRUE, message, + filename, lineno); + NS_ENSURE_SUCCESS(rv, rv); + + event->SetTarget(static_cast(mWorker)); + + PRBool stopPropagation = PR_FALSE; + rv = mWorker->DispatchEvent(static_cast(event), + &stopPropagation); + if (NS_SUCCEEDED(rv) && stopPropagation) { + return NS_OK; + } + } + + nsRefPtr parent = mWorker->GetParent(); + if (!parent) { + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + nsCOMPtr consoleService = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (consoleService) { + rv = consoleService->LogMessage(mScriptError); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; + } + + nsRefPtr runnable = + new nsReportErrorRunnable(parent, mScriptError); + if (runnable) { + nsRefPtr grandparent = parent->GetParent(); + rv = grandparent ? + nsDOMThreadService::get()->Dispatch(grandparent, runnable) : + NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; + } + +private: + nsRefPtr mWorker; + nsCOMPtr mWorkerWN; + nsCOMPtr mScriptError; +}; + +NS_IMPL_THREADSAFE_ISUPPORTS1(nsReportErrorRunnable, nsIRunnable) + +/** + * Used to post an expired timeout to the correct worker. + */ +class nsDOMWorkerTimeoutRunnable : public nsIRunnable +{ +public: + NS_DECL_ISUPPORTS + + nsDOMWorkerTimeoutRunnable(nsDOMWorkerTimeout* aTimeout) + : mTimeout(aTimeout) { } + + NS_IMETHOD Run() { + return mTimeout->Run(); + } +protected: + nsRefPtr mTimeout; +}; + +NS_IMPL_THREADSAFE_ISUPPORTS1(nsDOMWorkerTimeoutRunnable, nsIRunnable) + +class nsDOMWorkerKillRunnable : public nsIRunnable +{ +public: + NS_DECL_ISUPPORTS + + nsDOMWorkerKillRunnable(nsDOMWorker* aWorker) + : mWorker(aWorker) { } + + NS_IMETHOD Run() { + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + mWorker->Kill(); + return NS_OK; + } + +private: + nsRefPtr mWorker; +}; + +NS_IMPL_THREADSAFE_ISUPPORTS1(nsDOMWorkerKillRunnable, nsIRunnable) + +/** + * This class exists to solve a particular problem: Calling Dispatch on a + * thread pool will always create a new thread to service the runnable as long + * as the thread limit has not been reached. Since our DOM workers can only be + * accessed by one thread at a time we could end up spawning a new thread that + * does nothing but wait initially. There is no way to control this behavior + * currently so we cheat by using a runnable that emulates a thread. The + * nsDOMThreadService's monitor protects the queue of events. + */ +class nsDOMWorkerRunnable : public nsIRunnable +{ + friend class nsDOMThreadService; + +public: + NS_DECL_ISUPPORTS + + nsDOMWorkerRunnable(nsDOMWorker* aWorker) + : mWorker(aWorker), mCloseTimeoutInterval(0), mKillWorkerWhenDone(PR_FALSE) { + } + + virtual ~nsDOMWorkerRunnable() { + ClearQueue(); + } + + void PutRunnable(nsIRunnable* aRunnable, + PRIntervalTime aTimeoutInterval, + PRBool aClearQueue) { + NS_ASSERTION(aRunnable, "Null pointer!"); + + gDOMThreadService->mReentrantMonitor.AssertCurrentThreadIn(); + + if (NS_LIKELY(!aTimeoutInterval)) { + NS_ADDREF(aRunnable); + mRunnables.Push(aRunnable); + } + else { + NS_ASSERTION(!mCloseRunnable, "More than one close runnable?!"); + if (aClearQueue) { + ClearQueue(); + } + mCloseRunnable = aRunnable; + mCloseTimeoutInterval = aTimeoutInterval; + mKillWorkerWhenDone = PR_TRUE; + } + } + + void SetCloseRunnableTimeout(PRIntervalTime aTimeoutInterval) { + NS_ASSERTION(aTimeoutInterval, "No timeout specified!"); + NS_ASSERTION(aTimeoutInterval!= PR_INTERVAL_NO_TIMEOUT, "Bad timeout!"); + + // No need to enter the monitor because we should already be in it. + + NS_ASSERTION(mWorker->GetExpirationTime() == PR_INTERVAL_NO_TIMEOUT, + "Asked to set timeout on a runnable with no close handler!"); + + // This may actually overflow but we don't care - the worst that could + // happen is that the close handler could run for a slightly different + // amount of time and the spec leaves the time up to us anyway. + mWorker->SetExpirationTime(PR_IntervalNow() + aTimeoutInterval); + } + + NS_IMETHOD Run() { + NS_ASSERTION(!NS_IsMainThread(), + "This should *never* run on the main thread!"); + + // This must have been set up by the thread service + NS_ASSERTION(gJSContextIndex != BAD_TLS_INDEX, "No context index!"); + + // Make sure we have a JSContext to run everything on. + JSContext* cx = (JSContext*)PR_GetThreadPrivate(gJSContextIndex); + if (!cx) { + NS_ERROR("nsDOMThreadService didn't give us a context! Are we out of memory?"); + return NS_ERROR_FAILURE; + } + + NS_ASSERTION(!JS_GetGlobalObject(cx), "Shouldn't have a global!"); + + if (mWorker->IsPrivileged()) { + JS_SetVersion(cx, JSVERSION_LATEST); + } + else { + JS_SetVersion(cx, JSVERSION_DEFAULT); + } + + JS_SetContextPrivate(cx, mWorker); + + // Go ahead and trigger the operation callback for this context before we + // try to run any JS. That way we'll be sure to cancel or suspend as soon as + // possible if the compilation takes too long. + JS_TriggerOperationCallback(cx); + + PRBool killWorkerWhenDone; + { + nsLazyAutoRequest ar; + JSAutoEnterCompartment ac; + + // Tell the worker which context it will be using + if (mWorker->SetGlobalForContext(cx, &ar, &ac)) { + NS_ASSERTION(ar.entered(), "SetGlobalForContext must enter request on success"); + NS_ASSERTION(ac.entered(), "SetGlobalForContext must enter compartment on success"); + + RunQueue(cx, &killWorkerWhenDone); + + // Remove the global object from the context so that it might be garbage + // collected. + JS_SetGlobalObject(cx, NULL); + JS_SetContextPrivate(cx, NULL); + } + else { + NS_ASSERTION(!ar.entered(), "SetGlobalForContext must not enter request on failure"); + NS_ASSERTION(!ac.entered(), "SetGlobalForContext must not enter compartment on failure"); + + { + // Code in XPConnect assumes that the context's global object won't be + // replaced outside of a request. + JSAutoRequest ar2(cx); + + // This is usually due to a parse error in the worker script... + JS_SetGlobalObject(cx, NULL); + JS_SetContextPrivate(cx, NULL); + } + + ReentrantMonitorAutoEnter mon(gDOMThreadService->mReentrantMonitor); + killWorkerWhenDone = mKillWorkerWhenDone; + gDOMThreadService->WorkerComplete(this); + mon.NotifyAll(); + } + } + + if (killWorkerWhenDone) { + nsCOMPtr runnable = new nsDOMWorkerKillRunnable(mWorker); + NS_ENSURE_TRUE(runnable, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv = NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; + } + +protected: + void ClearQueue() { + nsCOMPtr runnable; + while ((runnable = dont_AddRef((nsIRunnable*)mRunnables.PopFront()))) { + // Loop until all the runnables are dead. + } + } + + void RunQueue(JSContext* aCx, PRBool* aCloseRunnableSet) { + while (1) { + nsCOMPtr runnable; + { + ReentrantMonitorAutoEnter mon(gDOMThreadService->mReentrantMonitor); + + runnable = dont_AddRef((nsIRunnable*)mRunnables.PopFront()); + + if (!runnable && mCloseRunnable) { + PRIntervalTime expirationTime; + if (mCloseTimeoutInterval == PR_INTERVAL_NO_TIMEOUT) { + expirationTime = mCloseTimeoutInterval; + } + else { + expirationTime = PR_IntervalNow() + mCloseTimeoutInterval; + } + mWorker->SetExpirationTime(expirationTime); + + runnable.swap(mCloseRunnable); + } + + if (!runnable || mWorker->IsCanceled()) { +#ifdef PR_LOGGING + if (mWorker->IsCanceled()) { + LOG(("Bailing out of run loop for canceled worker[0x%p]", + static_cast(mWorker.get()))); + } +#endif + *aCloseRunnableSet = mKillWorkerWhenDone; + gDOMThreadService->WorkerComplete(this); + mon.NotifyAll(); + return; + } + } + + // Clear out any old cruft hanging around in the regexp statics. + if (JSObject *global = JS_GetGlobalObject(aCx)) + JS_ClearRegExpStatics(aCx, global); + + runnable->Run(); + } + NS_NOTREACHED("Shouldn't ever get here!"); + } + + // Set at construction + nsRefPtr mWorker; + + // Protected by mReentrantMonitor + nsDeque mRunnables; + nsCOMPtr mCloseRunnable; + PRIntervalTime mCloseTimeoutInterval; + PRBool mKillWorkerWhenDone; +}; + +NS_IMPL_THREADSAFE_ISUPPORTS1(nsDOMWorkerRunnable, nsIRunnable) + +/******************************************************************************* + * JS environment function and callbacks + */ + +JSBool +DOMWorkerOperationCallback(JSContext* aCx) +{ + nsDOMWorker* worker = (nsDOMWorker*)JS_GetContextPrivate(aCx); + NS_ASSERTION(worker, "This must never be null!"); + + PRBool canceled = worker->IsCanceled(); + if (!canceled && worker->IsSuspended()) { + JSAutoSuspendRequest suspended(aCx); + + // Since we're going to block this thread we should open up a new thread + // in the thread pool for other workers. Must check the return value to + // make sure we don't decrement when we failed. + PRBool extraThreadAllowed = + NS_SUCCEEDED(gDOMThreadService->ChangeThreadPoolMaxThreads(1)); + + // Flush JIT caches now before suspending to avoid holding memory that we + // are not going to use. + JS_FlushCaches(aCx); + + for (;;) { + ReentrantMonitorAutoEnter mon(worker->Pool()->GetReentrantMonitor()); + + // There's a small chance that the worker was canceled after our check + // above in which case we shouldn't wait here. We're guaranteed not to + // race here because the pool reenters its monitor after canceling each + // worker in order to notify its condition variable. + canceled = worker->IsCanceled(); + if (!canceled && worker->IsSuspended()) { + mon.Wait(); + } + else { + break; + } + } + + if (extraThreadAllowed) { + gDOMThreadService->ChangeThreadPoolMaxThreads(-1); + } + } + + if (canceled) { + LOG(("Forcefully killing JS for worker [0x%p]", + static_cast(worker))); + // Kill execution of the currently running JS. + JS_ClearPendingException(aCx); + return JS_FALSE; + } + return JS_TRUE; +} + +void +DOMWorkerErrorReporter(JSContext* aCx, + const char* aMessage, + JSErrorReport* aReport) +{ + NS_ASSERTION(!NS_IsMainThread(), "Huh?!"); + + nsDOMWorker* worker = (nsDOMWorker*)JS_GetContextPrivate(aCx); + + if (worker->IsCanceled()) { + // We don't want to report errors from canceled workers. It's very likely + // that we only returned an error in the first place because the worker was + // already canceled. + return; + } + + if (worker->mErrorHandlerRecursionCount == 2) { + // We've somehow ended up in a recursive onerror loop. Bail out. + return; + } + + nsresult rv; + nsCOMPtr scriptError; + + { + // CreateInstance will lock, make sure we suspend our request! + JSAutoSuspendRequest ar(aCx); + + scriptError = do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv); + } + + if (NS_FAILED(rv)) { + return; + } + + nsCOMPtr scriptError2(do_QueryInterface(scriptError)); + + nsAutoString message, filename, line; + PRUint32 lineNumber, columnNumber, flags, errorNumber; + + if (aReport) { + if (aReport->ucmessage) { + message.Assign(reinterpret_cast(aReport->ucmessage)); + } + filename.AssignWithConversion(aReport->filename); + line.Assign(reinterpret_cast(aReport->uclinebuf)); + lineNumber = aReport->lineno; + columnNumber = aReport->uctokenptr - aReport->uclinebuf; + flags = aReport->flags; + errorNumber = aReport->errorNumber; + } + else { + lineNumber = columnNumber = errorNumber = 0; + flags = nsIScriptError::errorFlag | nsIScriptError::exceptionFlag; + } + + if (message.IsEmpty()) { + message.AssignWithConversion(aMessage); + } + + rv = scriptError2->InitWithWindowID(message.get(), filename.get(), line.get(), + lineNumber, columnNumber, flags, + "DOM Worker javascript", + worker->Pool()->WindowID()); + + if (NS_FAILED(rv)) { + return; + } + + // Don't call the error handler if we're out of stack space. + if (errorNumber != JSMSG_OVER_RECURSED) { + // Try the onerror handler for the worker's scope. + nsRefPtr scope = worker->GetInnerScope(); + NS_ASSERTION(scope, "Null scope!"); + + PRBool hasListeners = scope->HasListeners(NS_LITERAL_STRING("error")); + if (hasListeners) { + nsRefPtr event(new nsDOMWorkerErrorEvent()); + if (event) { + rv = event->InitErrorEvent(NS_LITERAL_STRING("error"), PR_FALSE, + PR_TRUE, nsDependentString(message), + filename, lineNumber); + if (NS_SUCCEEDED(rv)) { + event->SetTarget(scope); + + NS_ASSERTION(worker->mErrorHandlerRecursionCount >= 0, + "Bad recursion count logic!"); + worker->mErrorHandlerRecursionCount++; + + PRBool preventDefaultCalled = PR_FALSE; + scope->DispatchEvent(static_cast(event), + &preventDefaultCalled); + + worker->mErrorHandlerRecursionCount--; + + if (preventDefaultCalled) { + return; + } + } + } + } + } + + // Still unhandled, fire at the onerror handler on the worker. + nsCOMPtr runnable = + new nsReportErrorRunnable(worker, scriptError); + NS_ENSURE_TRUE(runnable,); + + nsRefPtr parent = worker->GetParent(); + + // If this worker has a parent then we need to send the message through the + // thread service to be run on the parent's thread. Otherwise it is a + // top-level worker and we send the message to the main thread. + rv = parent ? nsDOMThreadService::get()->Dispatch(parent, runnable) + : NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + return; + } +} + +/******************************************************************************* + * nsDOMThreadService + */ + +nsDOMThreadService::nsDOMThreadService() +: mReentrantMonitor("nsDOMThreadServer.mReentrantMonitor"), + mNavigatorStringsLoaded(PR_FALSE) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); +#ifdef PR_LOGGING + if (!gDOMThreadsLog) { + gDOMThreadsLog = PR_NewLogModule("nsDOMThreads"); + } +#endif + LOG(("Initializing DOM Thread service")); +} + +nsDOMThreadService::~nsDOMThreadService() +{ + LOG(("DOM Thread service destroyed")); + + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + Cleanup(); +} + +NS_IMPL_THREADSAFE_ISUPPORTS3(nsDOMThreadService, nsIEventTarget, + nsIObserver, + nsIThreadPoolListener) + +nsresult +nsDOMThreadService::Init() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(!gDOMThreadService, "Only one instance should ever be created!"); + + nsresult rv; + nsCOMPtr obs = + do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + + obs.forget(&gObserverService); + + RegisterPrefCallbacks(); + + mThreadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mThreadPool->SetListener(this); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mThreadPool->SetThreadLimit(THREADPOOL_MAX_THREADS); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mThreadPool->SetIdleThreadLimit(THREADPOOL_IDLE_THREADS); + NS_ENSURE_SUCCESS(rv, rv); + + PRBool success = mWorkersInProgress.Init(); + NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY); + + success = mPools.Init(); + NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY); + + success = mThreadsafeContractIDs.Init(); + NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY); + + success = mJSContexts.SetCapacity(THREADPOOL_THREAD_CAP); + NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY); + + nsCOMPtr + runtimeSvc(do_GetService("@mozilla.org/js/xpc/RuntimeService;1")); + NS_ENSURE_TRUE(runtimeSvc, NS_ERROR_FAILURE); + runtimeSvc.forget(&gJSRuntimeService); + + nsCOMPtr + contextStack(do_GetService("@mozilla.org/js/xpc/ContextStack;1")); + NS_ENSURE_TRUE(contextStack, NS_ERROR_FAILURE); + contextStack.forget(&gThreadJSContextStack); + + nsCOMPtr secMan(new nsDOMWorkerSecurityManager()); + NS_ENSURE_TRUE(secMan, NS_ERROR_OUT_OF_MEMORY); + secMan.forget(&gWorkerSecurityManager); + + if (gJSContextIndex == BAD_TLS_INDEX && + PR_NewThreadPrivateIndex(&gJSContextIndex, NULL) != PR_SUCCESS) { + NS_ERROR("PR_NewThreadPrivateIndex failed!"); + gJSContextIndex = BAD_TLS_INDEX; + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +/* static */ +already_AddRefed +nsDOMThreadService::GetOrInitService() +{ + if (!gDOMThreadService) { + nsRefPtr service = new nsDOMThreadService(); + NS_ENSURE_TRUE(service, nsnull); + + nsresult rv = service->Init(); + NS_ENSURE_SUCCESS(rv, nsnull); + + service.swap(gDOMThreadService); + } + + nsRefPtr service(gDOMThreadService); + return service.forget(); +} + +/* static */ +nsDOMThreadService* +nsDOMThreadService::get() +{ + return gDOMThreadService; +} + +/* static */ +JSContext* +nsDOMThreadService::GetCurrentContext() +{ + JSContext* cx; + + if (NS_IsMainThread()) { + nsresult rv = ThreadJSContextStack()->GetSafeJSContext(&cx); + NS_ENSURE_SUCCESS(rv, nsnull); + } + else { + NS_ENSURE_TRUE(gJSContextIndex, nsnull); + cx = static_cast(PR_GetThreadPrivate(gJSContextIndex)); + } + + return cx; +} + +/* static */ +void +nsDOMThreadService::Shutdown() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_IF_RELEASE(gDOMThreadService); +} + +void +nsDOMThreadService::Cleanup() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + // This will either be called at 'xpcom-shutdown' or earlier if the call to + // Init fails somehow. We can therefore assume that all services will still + // be available here. + + // Cancel all workers that weren't tied to a window. + CancelWorkersForGlobal(nsnull); + + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + NS_ASSERTION(!mPools.Count(), "Live workers left!"); + mPools.Clear(); + + NS_ASSERTION(!mSuspendedWorkers.Length(), "Suspended workers left!"); + mSuspendedWorkers.Clear(); + } + + if (gObserverService) { + gObserverService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + NS_RELEASE(gObserverService); + + UnregisterPrefCallbacks(); + } + + // The thread pool holds a circular reference to this service through its + // listener. We must shut down the thread pool manually to break this cycle. + if (mThreadPool) { + mThreadPool->Shutdown(); + mThreadPool = nsnull; + } + + // Need to force a GC so that all of our workers get cleaned up. + if (gThreadJSContextStack) { + JSContext* safeContext; + if (NS_SUCCEEDED(gThreadJSContextStack->GetSafeJSContext(&safeContext))) { + JS_GC(safeContext); + } + NS_RELEASE(gThreadJSContextStack); + } + + // These must be released after the thread pool is shut down. + NS_IF_RELEASE(gJSRuntimeService); + NS_IF_RELEASE(gWorkerSecurityManager); +} + +nsresult +nsDOMThreadService::Dispatch(nsDOMWorker* aWorker, + nsIRunnable* aRunnable, + PRIntervalTime aTimeoutInterval, + PRBool aClearQueue) +{ + NS_ASSERTION(aWorker, "Null pointer!"); + NS_ASSERTION(aRunnable, "Null pointer!"); + + if (!mThreadPool) { + // This can happen from a nsDOMWorker::Finalize call after the thread pool + // has been shutdown. It should never be possible off the main thread. + NS_ASSERTION(NS_IsMainThread(), + "This should be impossible on a non-main thread!"); + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + + // Don't accept the runnable if the worker's close handler has been triggered + // (unless, of course, this is the close runnable as indicated by the non-0 + // timeout value). + if (aWorker->IsClosing() && !aTimeoutInterval) { + LOG(("Will not dispatch runnable [0x%p] for closing worker [0x%p]", + static_cast(aRunnable), static_cast(aWorker))); + return NS_ERROR_NOT_AVAILABLE; + } + + nsRefPtr workerRunnable; + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + if (mWorkersInProgress.Get(aWorker, getter_AddRefs(workerRunnable))) { + workerRunnable->PutRunnable(aRunnable, aTimeoutInterval, aClearQueue); + return NS_OK; + } + + workerRunnable = new nsDOMWorkerRunnable(aWorker); + NS_ENSURE_TRUE(workerRunnable, NS_ERROR_OUT_OF_MEMORY); + + workerRunnable->PutRunnable(aRunnable, aTimeoutInterval, PR_FALSE); + + PRBool success = mWorkersInProgress.Put(aWorker, workerRunnable); + NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY); + } + + nsresult rv = mThreadPool->Dispatch(workerRunnable, NS_DISPATCH_NORMAL); + + // XXX This is a mess and it could probably be removed once we have an + // infallible malloc implementation. + if (NS_FAILED(rv)) { + NS_WARNING("Failed to dispatch runnable to thread pool!"); + + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + // We exited the monitor after inserting the runnable into the table so make + // sure we're removing the right one! + nsRefPtr tableRunnable; + if (mWorkersInProgress.Get(aWorker, getter_AddRefs(tableRunnable)) && + workerRunnable == tableRunnable) { + mWorkersInProgress.Remove(aWorker); + + // And don't forget to tell anyone who's waiting. + mon.NotifyAll(); + } + + return rv; + } + + return NS_OK; +} + +void +nsDOMThreadService::SetWorkerTimeout(nsDOMWorker* aWorker, + PRIntervalTime aTimeoutInterval) +{ + NS_ASSERTION(aWorker, "Null pointer!"); + NS_ASSERTION(aTimeoutInterval, "No timeout specified!"); + + NS_ASSERTION(mThreadPool, "Dispatch called after 'xpcom-shutdown'!"); + + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + nsRefPtr workerRunnable; + if (mWorkersInProgress.Get(aWorker, getter_AddRefs(workerRunnable))) { + workerRunnable->SetCloseRunnableTimeout(aTimeoutInterval); + } +} + +void +nsDOMThreadService::WorkerComplete(nsDOMWorkerRunnable* aRunnable) +{ + mReentrantMonitor.AssertCurrentThreadIn(); + +#ifdef DEBUG + nsRefPtr& debugWorker = aRunnable->mWorker; + + nsRefPtr runnable; + NS_ASSERTION(mWorkersInProgress.Get(debugWorker, getter_AddRefs(runnable)) && + runnable == aRunnable, + "Removing a worker that isn't in our hashtable?!"); +#endif + + mWorkersInProgress.Remove(aRunnable->mWorker); +} + +PRBool +nsDOMThreadService::QueueSuspendedWorker(nsDOMWorkerRunnable* aRunnable) +{ + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + +#ifdef DEBUG + { + // Make sure that the runnable is in mWorkersInProgress. + nsRefPtr current; + mWorkersInProgress.Get(aRunnable->mWorker, getter_AddRefs(current)); + NS_ASSERTION(current == aRunnable, "Something crazy wrong here!"); + } +#endif + + return mSuspendedWorkers.AppendElement(aRunnable) ? PR_TRUE : PR_FALSE; +} + +/* static */ +JSContext* +nsDOMThreadService::CreateJSContext() +{ + JSRuntime* rt; + gJSRuntimeService->GetRuntime(&rt); + NS_ENSURE_TRUE(rt, nsnull); + + JSAutoContextDestroyer cx(JS_NewContext(rt, 8192)); + NS_ENSURE_TRUE(cx, nsnull); + + JS_SetErrorReporter(cx, DOMWorkerErrorReporter); + + JS_SetOperationCallback(cx, DOMWorkerOperationCallback); + + static JSSecurityCallbacks securityCallbacks = { + nsDOMWorkerSecurityManager::JSCheckAccess, + nsDOMWorkerSecurityManager::JSTranscodePrincipals, + nsDOMWorkerSecurityManager::JSFindPrincipal + }; + JS_SetContextSecurityCallbacks(cx, &securityCallbacks); + + JS_ClearContextDebugHooks(cx); + + nsresult rv = nsContentUtils::XPConnect()-> + SetSecurityManagerForJSContext(cx, gWorkerSecurityManager, 0); + NS_ENSURE_SUCCESS(rv, nsnull); + + JS_SetNativeStackQuota(cx, 256*1024); + + JS_SetOptions(cx, + JS_GetOptions(cx) | JSOPTION_METHODJIT | JSOPTION_JIT | JSOPTION_PROFILING); + JS_SetGCParameterForThread(cx, JSGC_MAX_CODE_CACHE_BYTES, 1 * 1024 * 1024); + + return cx.forget(); +} + +already_AddRefed +nsDOMThreadService::GetPoolForGlobal(nsIScriptGlobalObject* aGlobalObject, + PRBool aRemove) +{ + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + nsRefPtr pool; + mPools.Get(aGlobalObject, getter_AddRefs(pool)); + + if (aRemove) { + mPools.Remove(aGlobalObject); + } + + return pool.forget(); +} + +void +nsDOMThreadService::TriggerOperationCallbackForPool(nsDOMWorkerPool* aPool) +{ + mReentrantMonitor.AssertCurrentThreadIn(); + + // See if we need to trigger the operation callback on any currently running + // contexts. + PRUint32 contextCount = mJSContexts.Length(); + for (PRUint32 index = 0; index < contextCount; index++) { + JSContext*& cx = mJSContexts[index]; + nsDOMWorker* worker = (nsDOMWorker*)JS_GetContextPrivate(cx); + if (worker && worker->Pool() == aPool) { + JS_TriggerOperationCallback(cx); + } + } +} + +void +nsDOMThreadService::RescheduleSuspendedWorkerForPool(nsDOMWorkerPool* aPool) +{ + mReentrantMonitor.AssertCurrentThreadIn(); + + PRUint32 count = mSuspendedWorkers.Length(); + if (!count) { + // Nothing to do here. + return; + } + + nsTArray others(count); + + for (PRUint32 index = 0; index < count; index++) { + nsDOMWorkerRunnable* runnable = mSuspendedWorkers[index]; + +#ifdef DEBUG + { + // Make sure that the runnable never left mWorkersInProgress. + nsRefPtr current; + mWorkersInProgress.Get(runnable->mWorker, getter_AddRefs(current)); + NS_ASSERTION(current == runnable, "Something crazy wrong here!"); + } +#endif + + if (runnable->mWorker->Pool() == aPool) { +#ifdef DEBUG + nsresult rv = +#endif + mThreadPool->Dispatch(runnable, NS_DISPATCH_NORMAL); + NS_ASSERTION(NS_SUCCEEDED(rv), "This shouldn't ever fail!"); + } + else { + others.AppendElement(runnable); + } + } + + mSuspendedWorkers.SwapElements(others); +} + +void +nsDOMThreadService::CancelWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject) +{ + nsRefPtr pool = GetPoolForGlobal(aGlobalObject, PR_TRUE); + if (pool) { + pool->Cancel(); + + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + TriggerOperationCallbackForPool(pool); + RescheduleSuspendedWorkerForPool(pool); + } +} + +void +nsDOMThreadService::SuspendWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject) +{ + NS_ASSERTION(aGlobalObject, "Null pointer!"); + + nsRefPtr pool = GetPoolForGlobal(aGlobalObject, PR_FALSE); + if (pool) { + pool->Suspend(); + + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + TriggerOperationCallbackForPool(pool); + } +} + +void +nsDOMThreadService::ResumeWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject) +{ + NS_ASSERTION(aGlobalObject, "Null pointer!"); + + nsRefPtr pool = GetPoolForGlobal(aGlobalObject, PR_FALSE); + if (pool) { + pool->Resume(); + + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + TriggerOperationCallbackForPool(pool); + RescheduleSuspendedWorkerForPool(pool); + } +} + +void +nsDOMThreadService::NoteEmptyPool(nsDOMWorkerPool* aPool) +{ + NS_ASSERTION(aPool, "Null pointer!"); + + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + mPools.Remove(aPool->ScriptGlobalObject()); +} + +void +nsDOMThreadService::TimeoutReady(nsDOMWorkerTimeout* aTimeout) +{ + nsRefPtr runnable = + new nsDOMWorkerTimeoutRunnable(aTimeout); + NS_ENSURE_TRUE(runnable,); + + Dispatch(aTimeout->GetWorker(), runnable); +} + +nsresult +nsDOMThreadService::ChangeThreadPoolMaxThreads(PRInt16 aDelta) +{ + NS_ENSURE_ARG(aDelta == 1 || aDelta == -1); + + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + 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); + + // If we're allowing an extra thread then post a dummy event to the thread + // pool so that any pending workers can get started. The thread pool doesn't + // do this on its own like it probably should... + if (aDelta == 1) { + nsCOMPtr dummy(new nsRunnable()); + if (dummy) { + rv = mThreadPool->Dispatch(dummy, NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + return NS_OK; +} + +void +nsDOMThreadService::NoteThreadsafeContractId(const nsACString& aContractId, + PRBool aIsThreadsafe) +{ + NS_ASSERTION(!aContractId.IsEmpty(), "Empty contract id!"); + + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + +#ifdef DEBUG + { + PRBool isThreadsafe; + if (mThreadsafeContractIDs.Get(aContractId, &isThreadsafe)) { + NS_ASSERTION(aIsThreadsafe == isThreadsafe, "Inconsistent threadsafety!"); + } + } +#endif + + if (!mThreadsafeContractIDs.Put(aContractId, aIsThreadsafe)) { + NS_WARNING("Out of memory!"); + } +} + +ThreadsafeStatus +nsDOMThreadService::GetContractIdThreadsafeStatus(const nsACString& aContractId) +{ + NS_ASSERTION(!aContractId.IsEmpty(), "Empty contract id!"); + + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + PRBool isThreadsafe; + if (mThreadsafeContractIDs.Get(aContractId, &isThreadsafe)) { + return isThreadsafe ? Threadsafe : NotThreadsafe; + } + + return Unknown; +} + +// static +nsIJSRuntimeService* +nsDOMThreadService::JSRuntimeService() +{ + return gJSRuntimeService; +} + +// static +nsIThreadJSContextStack* +nsDOMThreadService::ThreadJSContextStack() +{ + return gThreadJSContextStack; +} + +// static +nsIXPCSecurityManager* +nsDOMThreadService::WorkerSecurityManager() +{ + return gWorkerSecurityManager; +} + +/** + * See nsIEventTarget + */ +NS_IMETHODIMP +nsDOMThreadService::Dispatch(nsIRunnable* aEvent, + PRUint32 aFlags) +{ + NS_ENSURE_ARG_POINTER(aEvent); + NS_ENSURE_FALSE(aFlags & NS_DISPATCH_SYNC, NS_ERROR_NOT_IMPLEMENTED); + + // This should only ever be called by the timer code! We run the event right + // now, but all that does is queue the real event for the proper worker. + aEvent->Run(); + + return NS_OK; +} + +/** + * See nsIEventTarget + */ +NS_IMETHODIMP +nsDOMThreadService::IsOnCurrentThread(PRBool* _retval) +{ + NS_NOTREACHED("No one should call this!"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +/** + * See nsIObserver + */ +NS_IMETHODIMP +nsDOMThreadService::Observe(nsISupports* aSubject, + const char* aTopic, + const PRUnichar* aData) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + Cleanup(); + return NS_OK; + } + + NS_NOTREACHED("Unknown observer topic!"); + return NS_OK; +} + +/** + * See nsIThreadPoolListener + */ +NS_IMETHODIMP +nsDOMThreadService::OnThreadCreated() +{ + LOG(("Thread created")); + + nsIThread* current = NS_GetCurrentThread(); + + // We want our worker threads to always have a lower priority than the main + // thread. NSPR docs say that this isn't incredibly reliable across all + // platforms but we hope for the best. + nsCOMPtr priority(do_QueryInterface(current)); + NS_ENSURE_TRUE(priority, NS_ERROR_FAILURE); + + nsresult rv = priority->SetPriority(nsISupportsPriority::PRIORITY_LOWEST); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ASSERTION(gJSContextIndex != BAD_TLS_INDEX, "No context index!"); + + // Set the context up for the worker. + JSContext* cx = (JSContext*)PR_GetThreadPrivate(gJSContextIndex); + if (!cx) { + cx = nsDOMThreadService::CreateJSContext(); + NS_ENSURE_TRUE(cx, NS_ERROR_FAILURE); + + PRStatus status = PR_SetThreadPrivate(gJSContextIndex, cx); + if (status != PR_SUCCESS) { + NS_WARNING("Failed to set context on thread!"); + nsContentUtils::XPConnect()->ReleaseJSContext(cx, PR_TRUE); + return NS_ERROR_FAILURE; + } + + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + +#ifdef DEBUG + JSContext** newContext = +#endif + mJSContexts.AppendElement(cx); + + // We ensure the capacity of this array in Init. + NS_ASSERTION(newContext, "Should never fail!"); + } + + // Make sure that XPConnect knows about this context. + gThreadJSContextStack->Push(cx); + gThreadJSContextStack->SetSafeJSContext(cx); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMThreadService::OnThreadShuttingDown() +{ + LOG(("Thread shutting down")); + + NS_ASSERTION(gJSContextIndex != BAD_TLS_INDEX, "No context index!"); + + JSContext* cx = (JSContext*)PR_GetThreadPrivate(gJSContextIndex); + NS_WARN_IF_FALSE(cx, "Thread died with no context?"); + if (cx) { + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + mJSContexts.RemoveElement(cx); + } + + JSContext* pushedCx; + gThreadJSContextStack->Pop(&pushedCx); + NS_ASSERTION(pushedCx == cx, "Popped the wrong context!"); + + gThreadJSContextStack->SetSafeJSContext(nsnull); + + // The cycle collector may be running on the main thread. If so we cannot + // simply destroy this context. Instead we proxy the context destruction to + // the main thread. If that fails somehow then we simply leak the context. + nsCOMPtr runnable = new nsDestroyJSContextRunnable(cx); + + if (NS_FAILED(NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL))) { + NS_WARNING("Failed to dispatch release runnable!"); + } + } + + return NS_OK; +} + +nsresult +nsDOMThreadService::RegisterWorker(nsDOMWorker* aWorker, + nsIScriptGlobalObject* aGlobalObject) +{ + NS_ASSERTION(aWorker, "Null pointer!"); + + if (aGlobalObject && NS_IsMainThread()) { + nsCOMPtr domWindow(do_QueryInterface(aGlobalObject)); + NS_ENSURE_TRUE(domWindow, NS_ERROR_NO_INTERFACE); + + nsPIDOMWindow* innerWindow = domWindow->IsOuterWindow() ? + domWindow->GetCurrentInnerWindow() : + domWindow.get(); + NS_ENSURE_STATE(innerWindow); + + nsCOMPtr newGlobal(do_QueryInterface(innerWindow)); + NS_ENSURE_TRUE(newGlobal, NS_ERROR_NO_INTERFACE); + + aGlobalObject = newGlobal; + } + + nsRefPtr pool; + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + if (!mThreadPool) { + // Shutting down! + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + + mPools.Get(aGlobalObject, getter_AddRefs(pool)); + } + + nsresult rv; + + if (!pool) { + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + if (!mNavigatorStringsLoaded) { + rv = NS_GetNavigatorAppName(mAppName); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_GetNavigatorAppVersion(mAppVersion); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_GetNavigatorPlatform(mPlatform); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_GetNavigatorUserAgent(mUserAgent); + NS_ENSURE_SUCCESS(rv, rv); + + mNavigatorStringsLoaded = PR_TRUE; + } + + nsCOMPtr document; + if (aGlobalObject) { + nsCOMPtr domWindow(do_QueryInterface(aGlobalObject)); + NS_ENSURE_TRUE(domWindow, NS_ERROR_NO_INTERFACE); + + nsIDOMDocument* domDocument = domWindow->GetExtantDocument(); + NS_ENSURE_STATE(domDocument); + + document = do_QueryInterface(domDocument); + NS_ENSURE_STATE(document); + } + + pool = new nsDOMWorkerPool(aGlobalObject, document); + NS_ENSURE_TRUE(pool, NS_ERROR_OUT_OF_MEMORY); + + rv = pool->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + PRBool success = mPools.Put(aGlobalObject, pool); + NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY); + } + + rv = pool->NoteWorker(aWorker); + NS_ENSURE_SUCCESS(rv, rv); + + aWorker->SetPool(pool); + return NS_OK; +} + +void +nsDOMThreadService::GetAppName(nsAString& aAppName) +{ + NS_ASSERTION(mNavigatorStringsLoaded, + "Shouldn't call this before we have loaded strings!"); + aAppName.Assign(mAppName); +} + +void +nsDOMThreadService::GetAppVersion(nsAString& aAppVersion) +{ + NS_ASSERTION(mNavigatorStringsLoaded, + "Shouldn't call this before we have loaded strings!"); + aAppVersion.Assign(mAppVersion); +} + +void +nsDOMThreadService::GetPlatform(nsAString& aPlatform) +{ + NS_ASSERTION(mNavigatorStringsLoaded, + "Shouldn't call this before we have loaded strings!"); + aPlatform.Assign(mPlatform); +} + +void +nsDOMThreadService::GetUserAgent(nsAString& aUserAgent) +{ + NS_ASSERTION(mNavigatorStringsLoaded, + "Shouldn't call this before we have loaded strings!"); + aUserAgent.Assign(mUserAgent); +} + +void +nsDOMThreadService::RegisterPrefCallbacks() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + for (PRUint32 index = 0; index < NS_ARRAY_LENGTH(sPrefsToWatch); index++) { + Preferences::RegisterCallback(PrefCallback, sPrefsToWatch[index]); + PrefCallback(sPrefsToWatch[index], nsnull); + } +} + +void +nsDOMThreadService::UnregisterPrefCallbacks() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + for (PRUint32 index = 0; index < NS_ARRAY_LENGTH(sPrefsToWatch); index++) { + Preferences::UnregisterCallback(PrefCallback, sPrefsToWatch[index]); + } +} + +// static +int +nsDOMThreadService::PrefCallback(const char* aPrefName, + void* aClosure) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + if(!strcmp(aPrefName, "dom.max_script_run_time")) { + // We assume atomic 32bit reads/writes. If this assumption doesn't hold on + // some wacky platform then the worst that could happen is that the close + // handler will run for a slightly different amount of time. + PRUint32 timeoutMS = + Preferences::GetUint(aPrefName, gWorkerCloseHandlerTimeoutMS); + + // We must have a timeout value, 0 is not ok. If the pref is set to 0 then + // fall back to our default. + if (timeoutMS) { + gWorkerCloseHandlerTimeoutMS = timeoutMS; + } + } + return 0; +} + +// static +PRUint32 +nsDOMThreadService::GetWorkerCloseHandlerTimeoutMS() +{ + return gWorkerCloseHandlerTimeoutMS; +} diff --git a/dom/src/threads/nsDOMThreadService.h b/dom/src/threads/nsDOMThreadService.h new file mode 100644 index 000000000000..8a480ea898f1 --- /dev/null +++ b/dom/src/threads/nsDOMThreadService.h @@ -0,0 +1,212 @@ +/* -*- 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): + * Vladimir Vukicevic (Original Author) + * Ben Turner + * + * 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 __NSDOMTHREADSERVICE_H__ +#define __NSDOMTHREADSERVICE_H__ + +// Interfaces +#include "nsIEventTarget.h" +#include "nsIObserver.h" +#include "nsIThreadPool.h" + +// Other includes +#include "jsapi.h" +#include "mozilla/ReentrantMonitor.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsDataHashtable.h" +#include "nsRefPtrHashtable.h" +#include "nsStringGlue.h" +#include "nsTPtrArray.h" + +#include "prlog.h" +#ifdef PR_LOGGING +extern PRLogModuleInfo* gDOMThreadsLog; +#endif + +class nsDOMWorker; +class nsDOMWorkerPool; +class nsDOMWorkerRunnable; +class nsDOMWorkerTimeout; +class nsIJSRuntimeService; +class nsIScriptGlobalObject; +class nsIThreadJSContextStack; +class nsIXPConnect; +class nsIXPCSecurityManager; + +enum ThreadsafeStatus +{ + Threadsafe, + NotThreadsafe, + Unknown +}; + +class nsDOMThreadService : public nsIEventTarget, + public nsIObserver, + public nsIThreadPoolListener +{ + friend class nsDOMWorker; + friend class nsDOMWorkerNavigator; + friend class nsDOMWorkerPool; + friend class nsDOMWorkerRunnable; + friend class nsDOMWorkerThread; + friend class nsDOMWorkerTimeout; + friend class nsDOMWorkerXHR; + friend class nsDOMWorkerXHRProxy; + friend class nsLayoutStatics; + friend class nsReportErrorRunnable; + + friend void DOMWorkerErrorReporter(JSContext* aCx, + const char* aMessage, + JSErrorReport* aReport); + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIEVENTTARGET + NS_DECL_NSIOBSERVER + NS_DECL_NSITHREADPOOLLISTENER + + // Any DOM consumers that need access to this service should use this method. + static already_AddRefed GetOrInitService(); + + // Simple getter for this service. This does not create the service if it + // hasn't been created already, and it never AddRef's! + static nsDOMThreadService* get(); + + static JSContext* GetCurrentContext(); + + // Easy access to the services we care about. + static nsIJSRuntimeService* JSRuntimeService(); + static nsIThreadJSContextStack* ThreadJSContextStack(); + static nsIXPCSecurityManager* WorkerSecurityManager(); + + void CancelWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject); + void SuspendWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject); + void ResumeWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject); + + nsresult ChangeThreadPoolMaxThreads(PRInt16 aDelta); + + void NoteThreadsafeContractId(const nsACString& aContractId, + PRBool aIsThreadsafe); + + ThreadsafeStatus GetContractIdThreadsafeStatus(const nsACString& aContractId); + +private: + nsDOMThreadService(); + ~nsDOMThreadService(); + + nsresult Init(); + void Cleanup(); + + static void Shutdown(); + + nsresult Dispatch(nsDOMWorker* aWorker, + nsIRunnable* aRunnable, + PRIntervalTime aTimeoutInterval = 0, + PRBool aClearQueue = PR_FALSE); + + void SetWorkerTimeout(nsDOMWorker* aWorker, + PRIntervalTime aTimeoutInterval); + + void WorkerComplete(nsDOMWorkerRunnable* aRunnable); + + static JSContext* CreateJSContext(); + + already_AddRefed + GetPoolForGlobal(nsIScriptGlobalObject* aGlobalObject, + PRBool aRemove); + + void TriggerOperationCallbackForPool(nsDOMWorkerPool* aPool); + void RescheduleSuspendedWorkerForPool(nsDOMWorkerPool* aPool); + + void NoteEmptyPool(nsDOMWorkerPool* aPool); + + void TimeoutReady(nsDOMWorkerTimeout* aTimeout); + + nsresult RegisterWorker(nsDOMWorker* aWorker, + nsIScriptGlobalObject* aGlobalObject); + + void GetAppName(nsAString& aAppName); + void GetAppVersion(nsAString& aAppVersion); + void GetPlatform(nsAString& aPlatform); + void GetUserAgent(nsAString& aUserAgent); + + void RegisterPrefCallbacks(); + void UnregisterPrefCallbacks(); + + static int PrefCallback(const char* aPrefName, + void* aClosure); + + static PRUint32 GetWorkerCloseHandlerTimeoutMS(); + + PRBool QueueSuspendedWorker(nsDOMWorkerRunnable* aRunnable); + + // Our internal thread pool. + nsCOMPtr mThreadPool; + + // Maps nsIScriptGlobalObject* to nsDOMWorkerPool. + nsRefPtrHashtable mPools; + + // mReentrantMonitor protects all access to mWorkersInProgress and + // mCreationsInProgress. + mozilla::ReentrantMonitor mReentrantMonitor; + + // A map from nsDOMWorkerThread to nsDOMWorkerRunnable. + nsRefPtrHashtable mWorkersInProgress; + + // A list of active JSContexts that we've created. Always protected with + // mReentrantMonitor. + nsTArray mJSContexts; + + // A list of worker runnables that were never started because the worker was + // suspended. Always protected with mReentrantMonitor. + nsTArray mSuspendedWorkers; + + // Always protected with mReentrantMonitor. + nsDataHashtable mThreadsafeContractIDs; + + nsString mAppName; + nsString mAppVersion; + nsString mPlatform; + nsString mUserAgent; + + PRBool mNavigatorStringsLoaded; +}; + +#endif /* __NSDOMTHREADSERVICE_H__ */ diff --git a/dom/src/threads/nsDOMWorker.cpp b/dom/src/threads/nsDOMWorker.cpp new file mode 100644 index 000000000000..3fa82450f82a --- /dev/null +++ b/dom/src/threads/nsDOMWorker.cpp @@ -0,0 +1,2752 @@ +/* -*- Mode: c++; c-basic-offset: 2; 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 Web Workers. + * + * 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 (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 "nsDOMWorker.h" + +#include "nsIDOMEvent.h" +#include "nsIEventTarget.h" +#include "nsIJSRuntimeService.h" +#include "nsIXPConnect.h" + +#include "jscntxt.h" +#ifdef MOZ_SHARK +#include "jsdbgapi.h" +#endif +#include "nsAtomicRefcnt.h" +#include "nsAXPCNativeCallContext.h" +#include "nsContentUtils.h" +#include "nsDOMClassInfo.h" +#include "nsDOMClassInfoID.h" +#include "nsGlobalWindow.h" +#include "nsJSON.h" +#include "nsJSUtils.h" +#include "nsProxyRelease.h" +#include "nsThreadUtils.h" +#include "nsNativeCharsetUtils.h" +#include "xpcprivate.h" + +#include "nsDOMThreadService.h" +#include "nsDOMWorkerEvents.h" +#include "nsDOMWorkerLocation.h" +#include "nsDOMWorkerNavigator.h" +#include "nsDOMWorkerPool.h" +#include "nsDOMWorkerScriptLoader.h" +#include "nsDOMWorkerTimeout.h" +#include "nsDOMWorkerXHR.h" + +using namespace mozilla; + +class TestComponentThreadsafetyRunnable : public nsIRunnable +{ +public: + NS_DECL_ISUPPORTS + + TestComponentThreadsafetyRunnable(const nsACString& aContractId, + PRBool aService) + : mContractId(aContractId), + mService(aService), + mIsThreadsafe(PR_FALSE) + { } + + NS_IMETHOD Run() + { + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + nsresult rv; + nsCOMPtr instance; + if (mService) { + instance = do_GetService(mContractId.get(), &rv); + } + else { + instance = do_CreateInstance(mContractId.get(), &rv); + } + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr classInfo = do_QueryInterface(instance, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + PRUint32 flags; + rv = classInfo->GetFlags(&flags); + NS_ENSURE_SUCCESS(rv, rv); + + mIsThreadsafe = !!(flags & nsIClassInfo::THREADSAFE); + return NS_OK; + } + + PRBool IsThreadsafe() + { + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + return mIsThreadsafe; + } + +private: + nsCString mContractId; + PRBool mService; + PRBool mIsThreadsafe; +}; + +NS_IMPL_THREADSAFE_ISUPPORTS1(TestComponentThreadsafetyRunnable, nsIRunnable) + +class nsDOMWorkerFunctions +{ +public: + typedef nsDOMWorker::WorkerPrivilegeModel WorkerPrivilegeModel; + + // Same as window.dump(). + static JSBool + Dump(JSContext* aCx, uintN aArgc, jsval* aVp); + + // Same as window.setTimeout(). + static JSBool + SetTimeout(JSContext* aCx, uintN aArgc, jsval* aVp) { + return MakeTimeout(aCx, aArgc, aVp, PR_FALSE); + } + + // Same as window.setInterval(). + static JSBool + SetInterval(JSContext* aCx, uintN aArgc, jsval* aVp) { + return MakeTimeout(aCx, aArgc, aVp, PR_TRUE); + } + + // Used for both clearTimeout() and clearInterval(). + static JSBool + KillTimeout(JSContext* aCx, uintN aArgc, jsval* aVp); + + static JSBool + LoadScripts(JSContext* aCx, uintN aArgc, jsval* aVp); + + static JSBool + NewXMLHttpRequest(JSContext* aCx, uintN aArgc, jsval* aVp); + + static JSBool + NewWorker(JSContext* aCx, uintN aArgc, jsval* aVp) { + return MakeNewWorker(aCx, aArgc, aVp, nsDOMWorker::CONTENT); + } + + static JSBool + AtoB(JSContext* aCx, uintN aArgc, jsval* aVp); + + static JSBool + BtoA(JSContext* aCx, uintN aArgc, jsval* aVp); + + // Chrome-only functions + static JSBool + NewChromeWorker(JSContext* aCx, uintN aArgc, jsval* aVp); + + static JSBool + XPCOMLazyGetter(JSContext* aCx, JSObject* aObj, jsid aId, jsval* aVp); + + static JSBool + CreateInstance(JSContext* aCx, uintN aArgc, jsval* aVp) { + return GetInstanceCommon(aCx, aArgc, aVp, PR_FALSE); + } + + static JSBool + GetService(JSContext* aCx, uintN aArgc, jsval* aVp) { + return GetInstanceCommon(aCx, aArgc, aVp, PR_TRUE); + } + +#ifdef BUILD_CTYPES + static JSBool + CTypesLazyGetter(JSContext* aCx, JSObject* aObj, jsid aId, jsval* aVp); +#endif + +private: + // Internal helper for SetTimeout and SetInterval. + static JSBool + MakeTimeout(JSContext* aCx, uintN aArgc, jsval* aVp, PRBool aIsInterval); + + static JSBool + MakeNewWorker(JSContext* aCx, uintN aArgc, jsval* aVp, + WorkerPrivilegeModel aPrivilegeModel); + + static JSBool + GetInstanceCommon(JSContext* aCx, uintN aArgc, jsval* aVp, PRBool aService); +}; + +JSFunctionSpec gDOMWorkerXPCOMFunctions[] = { + {"createInstance", nsDOMWorkerFunctions::CreateInstance, 1, JSPROP_ENUMERATE}, + {"getService", nsDOMWorkerFunctions::GetService, 1, JSPROP_ENUMERATE}, + { nsnull, nsnull, 0, 0 } +}; + +JSBool +nsDOMWorkerFunctions::Dump(JSContext* aCx, + uintN aArgc, + jsval* aVp) +{ + JS_SET_RVAL(cx, aVp, JSVAL_VOID); + if (!nsGlobalWindow::DOMWindowDumpEnabled()) { + return JS_TRUE; + } + + JSString* str; + if (aArgc && (str = JS_ValueToString(aCx, JS_ARGV(aCx, aVp)[0])) && str) { + nsDependentJSString depStr; + if (depStr.init(aCx, str)) { + fputs(NS_ConvertUTF16toUTF8(depStr).get(), stderr); + fflush(stderr); + } + } + return JS_TRUE; +} + +JSBool +nsDOMWorkerFunctions::MakeTimeout(JSContext* aCx, + uintN aArgc, + jsval* aVp, + PRBool aIsInterval) +{ + nsDOMWorker* worker = static_cast(JS_GetContextPrivate(aCx)); + NS_ASSERTION(worker, "This should be set by the DOM thread service!"); + + if (worker->IsCanceled()) { + return JS_FALSE; + } + + PRUint32 id = worker->NextTimeoutId(); + + if (worker->IsClosing()) { + // Timeouts won't run in the close handler, fake success and bail. + JS_SET_RVAL(aCx, aVp, INT_TO_JSVAL(id)); + return JS_TRUE; + } + + nsRefPtr timeout = new nsDOMWorkerTimeout(worker, id); + if (!timeout) { + JS_ReportOutOfMemory(aCx); + return JS_FALSE; + } + + nsresult rv = timeout->Init(aCx, aArgc, JS_ARGV(aCx, aVp), aIsInterval); + if (NS_FAILED(rv)) { + JS_ReportError(aCx, "Failed to initialize timeout!"); + return JS_FALSE; + } + + rv = worker->AddFeature(timeout, aCx); + if (NS_FAILED(rv)) { + JS_ReportOutOfMemory(aCx); + return JS_FALSE; + } + + rv = timeout->Start(); + if (NS_FAILED(rv)) { + JS_ReportError(aCx, "Failed to start timeout!"); + return JS_FALSE; + } + + JS_SET_RVAL(aCx, aVp, INT_TO_JSVAL(id)); + return JS_TRUE; +} + +JSBool +nsDOMWorkerFunctions::KillTimeout(JSContext* aCx, + uintN aArgc, + jsval* aVp) +{ + nsDOMWorker* worker = static_cast(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 requires at least 1 parameter"); + return JS_FALSE; + } + + uint32 id; + if (!JS_ValueToECMAUint32(aCx, JS_ARGV(aCx, aVp)[0], &id)) { + JS_ReportError(aCx, "First argument must be a timeout id"); + return JS_FALSE; + } + + worker->CancelTimeoutWithId(PRUint32(id)); + JS_SET_RVAL(aCx, aVp, JSVAL_VOID); + return JS_TRUE; +} + +JSBool +nsDOMWorkerFunctions::LoadScripts(JSContext* aCx, + uintN aArgc, + jsval* aVp) +{ + nsDOMWorker* worker = static_cast(JS_GetContextPrivate(aCx)); + NS_ASSERTION(worker, "This should be set by the DOM thread service!"); + + if (worker->IsCanceled()) { + return JS_FALSE; + } + + if (!aArgc) { + // No argument is ok according to spec. + return JS_TRUE; + } + + nsAutoTArray urls; + + if (!urls.SetCapacity((PRUint32)aArgc)) { + JS_ReportOutOfMemory(aCx); + return JS_FALSE; + } + + jsval* argv = JS_ARGV(aCx, aVp); + for (uintN index = 0; index < aArgc; index++) { + jsval val = argv[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!"); + + nsDependentJSString depStr; + if (!depStr.init(aCx, str)) { + return JS_FALSE; + } + + newURL->Assign(depStr); + } + + nsRefPtr loader = + new nsDOMWorkerScriptLoader(worker); + if (!loader) { + JS_ReportOutOfMemory(aCx); + return JS_FALSE; + } + + nsresult rv = worker->AddFeature(loader, aCx); + if (NS_FAILED(rv)) { + JS_ReportOutOfMemory(aCx); + return JS_FALSE; + } + + rv = loader->LoadScripts(aCx, urls, PR_TRUE); + if (NS_FAILED(rv)) { + if (!JS_IsExceptionPending(aCx)) { + JS_ReportError(aCx, "Failed to load scripts"); + } + return JS_FALSE; + } + + JS_SET_RVAL(aCx, aVp, JSVAL_VOID); + return JS_TRUE; +} + +JSBool +nsDOMWorkerFunctions::NewXMLHttpRequest(JSContext* aCx, + uintN aArgc, + jsval* aVp) +{ + nsDOMWorker* worker = static_cast(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, "XMLHttpRequest constructor takes no arguments!"); + return JS_FALSE; + } + + nsRefPtr xhr = new nsDOMWorkerXHR(worker); + if (!xhr) { + JS_ReportOutOfMemory(aCx); + return JS_FALSE; + } + + nsresult rv = xhr->Init(); + if (NS_FAILED(rv)) { + JS_ReportError(aCx, "Failed to construct XMLHttpRequest!"); + return JS_FALSE; + } + + rv = worker->AddFeature(xhr, aCx); + if (NS_FAILED(rv)) { + JS_ReportOutOfMemory(aCx); + return JS_FALSE; + } + + nsCOMPtr xhrWrapped; + jsval v; + rv = nsContentUtils::WrapNative(aCx, JSVAL_TO_OBJECT(JS_CALLEE(aCx, aVp)), + static_cast(xhr), &v, + getter_AddRefs(xhrWrapped)); + if (NS_FAILED(rv)) { + JS_ReportError(aCx, "Failed to wrap XMLHttpRequest!"); + return JS_FALSE; + } + + JS_SET_RVAL(aCs, aVp, v); + return JS_TRUE; +} + +JSBool +nsDOMWorkerFunctions::AtoB(JSContext* aCx, + uintN aArgc, + jsval* aVp) +{ + nsDOMWorker* worker = static_cast(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 requires at least 1 parameter"); + return JS_FALSE; + } + + return nsXPConnect::Base64Decode(aCx, JS_ARGV(aCx, aVp)[0], + &JS_RVAL(aCx, aVp)); +} + +JSBool +nsDOMWorkerFunctions::BtoA(JSContext* aCx, + uintN aArgc, + jsval* aVp) +{ + nsDOMWorker* worker = static_cast(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 requires at least 1 parameter"); + return JS_FALSE; + } + + return nsXPConnect::Base64Encode(aCx, JS_ARGV(aCx, aVp)[0], + &JS_RVAL(aCx, aVp)); +} + +JSBool +nsDOMWorkerFunctions::NewChromeWorker(JSContext* aCx, + uintN aArgc, + jsval* aVp) +{ + nsDOMWorker* worker = static_cast(JS_GetContextPrivate(aCx)); + NS_ASSERTION(worker, "This should be set by the DOM thread service!"); + + if (!worker->IsPrivileged()) { + JS_ReportError(aCx, "Cannot create a priviliged worker!"); + return JS_FALSE; + } + + return MakeNewWorker(aCx, aArgc, aVp, nsDOMWorker::CHROME); +} + +JSBool +nsDOMWorkerFunctions::XPCOMLazyGetter(JSContext* aCx, + JSObject* aObj, + jsid aId, + jsval* aVp) +{ +#ifdef DEBUG + { + NS_ASSERTION(JS_GetGlobalForObject(aCx, aObj) == aObj, "Bad object!"); + NS_ASSERTION(JSID_IS_STRING(aId), "Not a string!"); + NS_ASSERTION(nsDependentJSString(aId).EqualsLiteral("XPCOM"), "Bad id!"); + } +#endif + nsDOMWorker* worker = static_cast(JS_GetContextPrivate(aCx)); + NS_ASSERTION(worker, "This should be set by the DOM thread service!"); + + if (worker->IsCanceled()) { + return JS_FALSE; + } + + PRUint16 dummy; + nsCOMPtr secMan; + nsContentUtils::XPConnect()-> + GetSecurityManagerForJSContext(aCx, getter_AddRefs(secMan), &dummy); + if (!secMan) { + JS_ReportError(aCx, "Could not get security manager!"); + return JS_FALSE; + } + + nsCID dummyCID; + if (NS_FAILED(secMan->CanGetService(aCx, dummyCID))) { + JS_ReportError(aCx, "Access to the XPCOM object is denied!"); + return JS_FALSE; + } + + JSObject* xpcom = JS_NewObject(aCx, nsnull, nsnull, nsnull); + NS_ENSURE_TRUE(xpcom, JS_FALSE); + + JSBool ok = JS_DefineFunctions(aCx, xpcom, gDOMWorkerXPCOMFunctions); + NS_ENSURE_TRUE(ok, JS_FALSE); + + ok = JS_DeletePropertyById(aCx, aObj, aId); + NS_ENSURE_TRUE(ok, JS_FALSE); + + jsval xpcomVal = OBJECT_TO_JSVAL(xpcom); + ok = JS_SetPropertyById(aCx, aObj, aId, &xpcomVal); + NS_ENSURE_TRUE(ok, JS_FALSE); + + JS_SET_RVAL(aCx, aVp, xpcomVal); + return JS_TRUE; +} + +JSBool +nsDOMWorkerFunctions::GetInstanceCommon(JSContext* aCx, + uintN aArgc, + jsval* aVp, + PRBool aService) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + nsDOMWorker* worker = static_cast(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 requires at least 1 parameter"); + return JS_FALSE; + } + + JSString* str = JS_ValueToString(aCx, JS_ARGV(aCx, aVp)[0]); + if (!str) { + NS_ASSERTION(JS_IsExceptionPending(aCx), "Need to set an exception!"); + return JS_FALSE; + } + + JSAutoByteString strBytes(aCx, str); + if (!strBytes) { + NS_ASSERTION(JS_IsExceptionPending(aCx), "Need to set an exception!"); + return JS_FALSE; + } + + nsDependentCString contractId(strBytes.ptr(), JS_GetStringLength(str)); + + nsDOMThreadService* threadService = nsDOMThreadService::get(); + + ThreadsafeStatus status = + threadService->GetContractIdThreadsafeStatus(contractId); + + if (status == Unknown) { + nsCOMPtr mainThread; + nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread)); + if (NS_FAILED(rv)) { + JS_ReportError(aCx, "Failed to get main thread!"); + return JS_FALSE; + } + + nsRefPtr runnable = + new TestComponentThreadsafetyRunnable(contractId, aService); + + rv = mainThread->Dispatch(runnable, NS_DISPATCH_SYNC); + if (NS_FAILED(rv)) { + JS_ReportError(aCx, "Failed to check threadsafety!"); + return JS_FALSE; + } + + // The worker may have been canceled while waiting above. Check again. + if (worker->IsCanceled()) { + return JS_FALSE; + } + + if (runnable->IsThreadsafe()) { + threadService->NoteThreadsafeContractId(contractId, PR_TRUE); + status = Threadsafe; + } + else { + threadService->NoteThreadsafeContractId(contractId, PR_FALSE); + status = NotThreadsafe; + } + } + + if (status == NotThreadsafe) { + JS_ReportError(aCx, "ChromeWorker may not create an XPCOM object that is " + "not threadsafe!"); + return JS_FALSE; + } + + nsCOMPtr instance; + if (aService) { + instance = do_GetService(contractId.get()); + if (!instance) { + JS_ReportError(aCx, "Could not get the service!"); + return JS_FALSE; + } + } + else { + instance = do_CreateInstance(contractId.get()); + if (!instance) { + JS_ReportError(aCx, "Could not create the instance!"); + return JS_FALSE; + } + } + + JSObject* global = JS_GetGlobalForObject(aCx, JS_GetScopeChain(aCx)); + if (!global) { + NS_ASSERTION(JS_IsExceptionPending(aCx), "Need to set an exception!"); + return JS_FALSE; + } + + jsval val; + nsCOMPtr wrapper; + if (NS_FAILED(nsContentUtils::WrapNative(aCx, global, instance, &val, + getter_AddRefs(wrapper)))) { + JS_ReportError(aCx, "Failed to wrap object!"); + return JS_FALSE; + } + + JS_SET_RVAL(aCx, aVp, val); + return JS_TRUE; +} + +JSBool +nsDOMWorkerFunctions::MakeNewWorker(JSContext* aCx, + uintN aArgc, + jsval* aVp, + WorkerPrivilegeModel aPrivilegeModel) +{ + JSObject *obj = JSVAL_TO_OBJECT(JS_CALLEE(aCx, aVp)); + + nsDOMWorker* worker = static_cast(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, "Worker constructor must have an argument!"); + return JS_FALSE; + } + + // This pointer is protected by our pool, but it is *not* threadsafe and must + // not be used in any way other than to pass it along to the Initialize call. + nsIScriptGlobalObject* owner = worker->Pool()->ScriptGlobalObject(); + + nsCOMPtr wrappedWorker = + worker->GetWrappedNative(); + if (!wrappedWorker) { + JS_ReportError(aCx, "Couldn't get wrapped native of worker!"); + return JS_FALSE; + } + + nsRefPtr newWorker = + new nsDOMWorker(worker, wrappedWorker, aPrivilegeModel); + if (!newWorker) { + JS_ReportOutOfMemory(aCx); + return JS_FALSE; + } + + nsresult rv = newWorker->InitializeInternal(owner, aCx, obj, aArgc, + JS_ARGV(aCx, aVp)); + if (NS_FAILED(rv)) { + JS_ReportError(aCx, "Couldn't initialize new worker!"); + return JS_FALSE; + } + + nsCOMPtr workerWrapped; + jsval v; + rv = nsContentUtils::WrapNative(aCx, obj, static_cast(newWorker), &v, + getter_AddRefs(workerWrapped)); + if (NS_FAILED(rv)) { + JS_ReportError(aCx, "Failed to wrap new worker!"); + return JS_FALSE; + } + + JS_SET_RVAL(aCx, aVp, v); + return JS_TRUE; +} + +#ifdef BUILD_CTYPES +static char* +UnicodeToNative(JSContext *cx, const jschar *source, size_t slen) +{ + nsCAutoString native; + nsDependentString unicode(reinterpret_cast(source), slen); + nsresult rv = NS_CopyUnicodeToNative(unicode, native); + if (NS_FAILED(rv)) { + JS_ReportError(cx, "could not convert string to native charset"); + return NULL; + } + + char* result = static_cast(JS_malloc(cx, native.Length() + 1)); + if (!result) + return NULL; + + memcpy(result, native.get(), native.Length() + 1); + return result; +} + +static JSCTypesCallbacks sCallbacks = { + UnicodeToNative +}; + +JSBool +nsDOMWorkerFunctions::CTypesLazyGetter(JSContext* aCx, + JSObject* aObj, + jsid aId, + jsval* aVp) +{ +#ifdef DEBUG + { + NS_ASSERTION(JS_GetGlobalForObject(aCx, aObj) == aObj, "Bad object!"); + NS_ASSERTION(JSID_IS_STRING(aId), "Not a string!"); + NS_ASSERTION(nsDependentJSString(aId).EqualsLiteral("ctypes"), "Bad id!"); + } +#endif + nsDOMWorker* worker = static_cast(JS_GetContextPrivate(aCx)); + NS_ASSERTION(worker, "This should be set by the DOM thread service!"); + NS_ASSERTION(worker->IsPrivileged(), "This shouldn't be possible!"); + + if (worker->IsCanceled()) { + return JS_FALSE; + } + + jsval ctypes; + return JS_DeletePropertyById(aCx, aObj, aId) && + JS_InitCTypesClass(aCx, aObj) && + JS_GetProperty(aCx, aObj, "ctypes", &ctypes) && + JS_SetCTypesCallbacks(aCx, JSVAL_TO_OBJECT(ctypes), &sCallbacks) && + JS_GetPropertyById(aCx, aObj, aId, aVp); +} +#endif +JSFunctionSpec gDOMWorkerFunctions[] = { + { "dump", nsDOMWorkerFunctions::Dump, 1, 0 }, + { "setTimeout", nsDOMWorkerFunctions::SetTimeout, 1, 0 }, + { "clearTimeout", nsDOMWorkerFunctions::KillTimeout, 1, 0 }, + { "setInterval", nsDOMWorkerFunctions::SetInterval, 1, 0 }, + { "clearInterval", nsDOMWorkerFunctions::KillTimeout, 1, 0 }, + { "importScripts", nsDOMWorkerFunctions::LoadScripts, 1, 0 }, + { "XMLHttpRequest", nsDOMWorkerFunctions::NewXMLHttpRequest, 0, JSFUN_CONSTRUCTOR }, + { "Worker", nsDOMWorkerFunctions::NewWorker, 1, JSFUN_CONSTRUCTOR }, + { "atob", nsDOMWorkerFunctions::AtoB, 1, 0 }, + { "btoa", nsDOMWorkerFunctions::BtoA, 1, 0 }, + { nsnull, nsnull, 0, 0 } +}; +JSFunctionSpec gDOMWorkerChromeFunctions[] = { + { "ChromeWorker", nsDOMWorkerFunctions::NewChromeWorker, 1, JSFUN_CONSTRUCTOR }, + { nsnull, nsnull, 0, 0 } +}; +enum DOMWorkerStructuredDataType +{ + // We have a special tag for XPCWrappedNatives that are being passed between + // threads. This will not work across processes and cannot be persisted. Only + // for ChromeWorker use at present. + DOMWORKER_SCTAG_WRAPPEDNATIVE = JS_SCTAG_USER_MIN + 0x1000, + + DOMWORKER_SCTAG_END +}; + +PR_STATIC_ASSERT(DOMWORKER_SCTAG_END <= JS_SCTAG_USER_MAX); + +// static +JSBool +WriteStructuredClone(JSContext* aCx, + JSStructuredCloneWriter* aWriter, + JSObject* aObj, + void* aClosure) +{ + NS_ASSERTION(aClosure, "Null pointer!"); + + // We'll stash any nsISupports pointers that need to be AddRef'd here. + nsTArray >* wrappedNatives = + static_cast >*>(aClosure); + + // See if this is a wrapped native. + nsCOMPtr wrappedNative; + nsContentUtils::XPConnect()-> + GetWrappedNativeOfJSObject(aCx, aObj, getter_AddRefs(wrappedNative)); + if (wrappedNative) { + // Get the raw nsISupports out of it. + nsISupports* wrappedObject = wrappedNative->Native(); + NS_ASSERTION(wrappedObject, "Null pointer?!"); + + // See if this nsISupports is threadsafe. + nsCOMPtr classInfo = do_QueryInterface(wrappedObject); + if (classInfo) { + PRUint32 flags; + if (NS_SUCCEEDED(classInfo->GetFlags(&flags)) && + (flags & nsIClassInfo::THREADSAFE)) { + // Write the raw pointer into the stream, and add it to the list we're + // building. + return JS_WriteUint32Pair(aWriter, DOMWORKER_SCTAG_WRAPPEDNATIVE, 0) && + JS_WriteBytes(aWriter, &wrappedObject, sizeof(wrappedObject)) && + wrappedNatives->AppendElement(wrappedObject); + } + } + } + + // Something failed above, try using the runtime callbacks instead. + const JSStructuredCloneCallbacks* runtimeCallbacks = + aCx->runtime->structuredCloneCallbacks; + if (runtimeCallbacks) { + return runtimeCallbacks->write(aCx, aWriter, aObj, nsnull); + } + + // We can't handle this object, throw an exception if one hasn't been thrown + // already. + if (!JS_IsExceptionPending(aCx)) { + nsDOMClassInfo::ThrowJSException(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); + } + return JS_FALSE; +} + +nsDOMWorkerScope::nsDOMWorkerScope(nsDOMWorker* aWorker) +: mWorker(aWorker), + mWrappedNative(nsnull), + mHasOnerror(PR_FALSE) +{ + NS_ASSERTION(aWorker, "Null pointer!"); +} + +NS_IMPL_ISUPPORTS_INHERITED3(nsDOMWorkerScope, nsDOMWorkerMessageHandler, + nsIWorkerScope, + nsIWorkerGlobalScope, + nsIXPCScriptable) + +NS_IMPL_CI_INTERFACE_GETTER4(nsDOMWorkerScope, nsIWorkerScope, + nsIWorkerGlobalScope, + nsIDOMEventTarget, + nsIXPCScriptable) + +NS_IMPL_THREADSAFE_DOM_CI_GETINTERFACES(nsDOMWorkerScope) +NS_IMPL_THREADSAFE_DOM_CI_ALL_THE_REST(nsDOMWorkerScope) + +// Need to return a scriptable helper so that XPConnect can get our +// nsIXPCScriptable flags properly (to not enumerate QI, for instance). +NS_IMETHODIMP +nsDOMWorkerScope::GetHelperForLanguage(PRUint32 aLanguage, + nsISupports** _retval) +{ + if (aLanguage == nsIProgrammingLanguage::JAVASCRIPT) { + NS_ADDREF(*_retval = NS_ISUPPORTS_CAST(nsIWorkerScope*, this)); + } + else { + *_retval = nsnull; + } + return NS_OK; +} + +// Use the xpc_map_end.h macros to generate the nsIXPCScriptable methods we want +// for the scope. + +#define XPC_MAP_CLASSNAME nsDOMWorkerScope +#define XPC_MAP_QUOTED_CLASSNAME "DedicatedWorkerGlobalScope" +#define XPC_MAP_WANT_POSTCREATE +#define XPC_MAP_WANT_TRACE +#define XPC_MAP_WANT_FINALIZE + +#define XPC_MAP_FLAGS \ + nsIXPCScriptable::USE_JSSTUB_FOR_ADDPROPERTY | \ + nsIXPCScriptable::USE_JSSTUB_FOR_DELPROPERTY | \ + nsIXPCScriptable::USE_JSSTUB_FOR_SETPROPERTY | \ + nsIXPCScriptable::DONT_ENUM_QUERY_INTERFACE | \ + nsIXPCScriptable::CLASSINFO_INTERFACES_ONLY | \ + nsIXPCScriptable::DONT_REFLECT_INTERFACE_NAMES | \ + nsIXPCScriptable::WANT_ADDPROPERTY + +#define XPC_MAP_WANT_ADDPROPERTY + +#include "xpc_map_end.h" + +NS_IMETHODIMP +nsDOMWorkerScope::PostCreate(nsIXPConnectWrappedNative* aWrapper, + JSContext* /* aCx */, + JSObject* /* aObj */) +{ + NS_ASSERTION(!mWrappedNative, "Already got a wrapper?!"); + mWrappedNative = aWrapper; + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerScope::Trace(nsIXPConnectWrappedNative* /* aWrapper */, + JSTracer* aTracer, + JSObject* /*aObj */) +{ + nsDOMWorkerMessageHandler::Trace(aTracer); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerScope::Finalize(nsIXPConnectWrappedNative* /* aWrapper */, + JSContext* /* aCx */, + JSObject* /* aObj */) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + ClearAllListeners(); + mWrappedNative = nsnull; + return NS_OK; +} + +already_AddRefed +nsDOMWorkerScope::GetWrappedNative() +{ + nsCOMPtr wrappedNative = mWrappedNative; + NS_ASSERTION(wrappedNative, "Null wrapped native!"); + return wrappedNative.forget(); +} + +NS_IMETHODIMP +nsDOMWorkerScope::AddProperty(nsIXPConnectWrappedNative* aWrapper, + JSContext* aCx, + JSObject* aObj, + jsid aId, + jsval* aVp, + PRBool* _retval) +{ + // We're not going to be setting any exceptions manually so set _retval to + // true in the beginning. + *_retval = PR_TRUE; + + // Bail out now if any of our prerequisites are not met. We only care about + // someone making an 'onmessage' or 'onerror' function so aId must be a + // string and aVp must be a function. + JSObject* funObj; + if (!(JSID_IS_STRING(aId) && + JSVAL_IS_OBJECT(*aVp) && + (funObj = JSVAL_TO_OBJECT(*aVp)) && + JS_ObjectIsFunction(aCx, funObj))) { + return NS_OK; + } + + JSFlatString *str = JSID_TO_FLAT_STRING(aId); + + // Figure out which listener we're setting. + SetListenerFunc func; + if (JS_FlatStringEqualsAscii(str, "onmessage")) { + func = &nsDOMWorkerScope::SetOnmessage; + } + else if (JS_FlatStringEqualsAscii(str, "onerror")) { + func = &nsDOMWorkerScope::SetOnerror; + } + else { + // Some other function, we don't need to do anything special after all. + return NS_OK; + } + + // Wrap the function as an nsIDOMEventListener. + nsCOMPtr listener; + nsresult rv = + nsContentUtils::XPConnect()->WrapJS(aCx, funObj, + NS_GET_IID(nsIDOMEventListener), + getter_AddRefs(listener)); + NS_ENSURE_SUCCESS(rv, rv); + + // And pass the listener to the appropriate setter. + rv = (this->*func)(listener); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerScope::GetSelf(nsIWorkerGlobalScope** aSelf) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + NS_ENSURE_ARG_POINTER(aSelf); + + if (mWorker->IsCanceled()) { + return NS_ERROR_ABORT; + } + + NS_ADDREF(*aSelf = this); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerScope::GetNavigator(nsIWorkerNavigator** _retval) +{ + if (!mNavigator) { + mNavigator = new nsDOMWorkerNavigator(); + NS_ENSURE_TRUE(mNavigator, NS_ERROR_OUT_OF_MEMORY); + } + + NS_ADDREF(*_retval = mNavigator); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerScope::GetLocation(nsIWorkerLocation** _retval) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + nsCOMPtr location = mWorker->GetLocation(); + NS_ASSERTION(location, "This should never be null!"); + + location.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerScope::GetOnerror(nsIDOMEventListener** aOnerror) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + NS_ENSURE_ARG_POINTER(aOnerror); + + if (mWorker->IsCanceled()) { + return NS_ERROR_ABORT; + } + + if (!mHasOnerror) { + // Spec says we have to return 'undefined' until something is set here. + nsIXPConnect* xpc = nsContentUtils::XPConnect(); + NS_ENSURE_TRUE(xpc, NS_ERROR_UNEXPECTED); + + nsAXPCNativeCallContext* cc; + nsresult rv = xpc->GetCurrentNativeCallContext(&cc); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(cc, NS_ERROR_UNEXPECTED); + + jsval* retval; + rv = cc->GetRetValPtr(&retval); + NS_ENSURE_SUCCESS(rv, rv); + + *retval = JSVAL_VOID; + return cc->SetReturnValueWasSet(PR_TRUE); + } + + nsCOMPtr listener = + GetOnXListener(NS_LITERAL_STRING("error")); + listener.forget(aOnerror); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerScope::SetOnerror(nsIDOMEventListener* aOnerror) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (mWorker->IsCanceled()) { + return NS_ERROR_ABORT; + } + + mHasOnerror = PR_TRUE; + + return SetOnXListener(NS_LITERAL_STRING("error"), aOnerror); +} + +NS_IMETHODIMP +nsDOMWorkerScope::PostMessage(/* JSObject aMessage */) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (mWorker->IsCanceled()) { + return NS_ERROR_ABORT; + } + + return mWorker->PostMessageInternal(PR_FALSE); +} + +NS_IMETHODIMP +nsDOMWorkerScope::Close() +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + return mWorker->Close(); +} + +NS_IMETHODIMP +nsDOMWorkerScope::GetOnmessage(nsIDOMEventListener** aOnmessage) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + NS_ENSURE_ARG_POINTER(aOnmessage); + + if (mWorker->IsCanceled()) { + return NS_ERROR_ABORT; + } + + nsCOMPtr listener = + GetOnXListener(NS_LITERAL_STRING("message")); + listener.forget(aOnmessage); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerScope::SetOnmessage(nsIDOMEventListener* aOnmessage) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (mWorker->IsCanceled()) { + return NS_ERROR_ABORT; + } + + return SetOnXListener(NS_LITERAL_STRING("message"), aOnmessage); +} + +NS_IMETHODIMP +nsDOMWorkerScope::GetOnclose(nsIDOMEventListener** aOnclose) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + NS_ENSURE_ARG_POINTER(aOnclose); + + if (mWorker->IsCanceled()) { + return NS_ERROR_ABORT; + } + + nsCOMPtr listener = + GetOnXListener(NS_LITERAL_STRING("close")); + listener.forget(aOnclose); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerScope::SetOnclose(nsIDOMEventListener* aOnclose) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (mWorker->IsCanceled()) { + return NS_ERROR_ABORT; + } + + nsresult rv = SetOnXListener(NS_LITERAL_STRING("close"), aOnclose); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerScope::RemoveEventListener(const nsAString& aType, + nsIDOMEventListener* aListener, + PRBool aUseCapture) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (mWorker->IsCanceled()) { + return NS_ERROR_ABORT; + } + + return nsDOMWorkerMessageHandler::RemoveEventListener(aType, aListener, + aUseCapture); +} + +NS_IMETHODIMP +nsDOMWorkerScope::DispatchEvent(nsIDOMEvent* aEvent, + PRBool* _retval) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (mWorker->IsCanceled()) { + return NS_ERROR_ABORT; + } + + return nsDOMWorkerMessageHandler::DispatchEvent(aEvent, _retval); +} + +NS_IMETHODIMP +nsDOMWorkerScope::AddEventListener(const nsAString& aType, + nsIDOMEventListener* aListener, + PRBool aUseCapture, + PRBool aWantsUntrusted, + PRUint8 optional_argc) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (mWorker->IsCanceled()) { + return NS_ERROR_ABORT; + } + + return nsDOMWorkerMessageHandler::AddEventListener(aType, aListener, + aUseCapture, + aWantsUntrusted, + optional_argc); +} + +class nsWorkerHoldingRunnable : public nsIRunnable +{ +public: + NS_DECL_ISUPPORTS + + nsWorkerHoldingRunnable(nsDOMWorker* aWorker) + : mWorker(aWorker), mWorkerWN(aWorker->GetWrappedNative()) { } + + NS_IMETHOD Run() { + return NS_OK; + } + + void ReplaceWrappedNative(nsIXPConnectWrappedNative* aWrappedNative) { + mWorkerWN = aWrappedNative; + } + +protected: + virtual ~nsWorkerHoldingRunnable() { } + + nsRefPtr mWorker; + +private: + nsCOMPtr mWorkerWN; +}; + +NS_IMPL_THREADSAFE_ISUPPORTS1(nsWorkerHoldingRunnable, nsIRunnable) + +class nsDOMFireEventRunnable : public nsWorkerHoldingRunnable +{ +public: + NS_DECL_ISUPPORTS_INHERITED + + nsDOMFireEventRunnable(nsDOMWorker* aWorker, + nsDOMWorkerEvent* aEvent, + PRBool aToInner) + : nsWorkerHoldingRunnable(aWorker), mEvent(aEvent), mToInner(aToInner) + { + NS_ASSERTION(aWorker && aEvent, "Null pointer!"); + } + + NS_IMETHOD Run() { +#ifdef DEBUG + if (NS_IsMainThread()) { + NS_ASSERTION(!mToInner, "Should only run outer events on main thread!"); + NS_ASSERTION(!mWorker->mParent, "Worker shouldn't have a parent!"); + } + else { + JSContext* cx = nsDOMThreadService::GetCurrentContext(); + nsDOMWorker* currentWorker = (nsDOMWorker*)JS_GetContextPrivate(cx); + NS_ASSERTION(currentWorker, "Must have a worker here!"); + + nsDOMWorker* targetWorker = mToInner ? mWorker.get() : mWorker->mParent; + NS_ASSERTION(currentWorker == targetWorker, "Wrong worker!"); + } +#endif + if (mWorker->IsCanceled()) { + return NS_ERROR_ABORT; + } + + // If the worker is suspended and we're running on the main thread then we + // can't actually dispatch the event yet. Instead we queue it for whenever + // we resume. + if (mWorker->IsSuspended() && NS_IsMainThread()) { + if (!mWorker->QueueSuspendedRunnable(this)) { + NS_ERROR("Out of memory?!"); + return NS_ERROR_ABORT; + } + return NS_OK; + } + + nsCOMPtr target = mToInner ? + static_cast(mWorker->GetInnerScope()) : + static_cast(mWorker); + + NS_ASSERTION(target, "Null target!"); + NS_ENSURE_TRUE(target, NS_ERROR_FAILURE); + + mEvent->SetTarget(target); + return target->DispatchEvent(mEvent, nsnull); + } + +protected: + nsRefPtr mEvent; + PRBool mToInner; +}; + +NS_IMPL_ISUPPORTS_INHERITED0(nsDOMFireEventRunnable, nsWorkerHoldingRunnable) + +// Standard NS_IMPL_THREADSAFE_ADDREF without the logging stuff (since this +// class is made to be inherited anyway). +NS_IMETHODIMP_(nsrefcnt) +nsDOMWorkerFeature::AddRef() +{ + NS_ASSERTION(mRefCnt >= 0, "Illegal refcnt!"); + return NS_AtomicIncrementRefcnt(mRefCnt); +} + +// Custom NS_IMPL_THREADSAFE_RELEASE. Checks the mFreeToDie flag before calling +// delete. If the flag is false then the feature still lives in the worker's +// list and must be removed. We rely on the fact that the RemoveFeature method +// calls AddRef and Release after setting the mFreeToDie flag so we won't leak. +NS_IMETHODIMP_(nsrefcnt) +nsDOMWorkerFeature::Release() +{ + NS_ASSERTION(mRefCnt, "Double release!"); + nsrefcnt count = NS_AtomicDecrementRefcnt(mRefCnt); + if (count == 0) { + if (mFreeToDie) { + mRefCnt = 1; + delete this; + } + else { + mWorker->RemoveFeature(this, nsnull); + } + } + return count; +} + +NS_IMPL_QUERY_INTERFACE0(nsDOMWorkerFeature) + +nsDOMWorker::nsDOMWorker(nsDOMWorker* aParent, + nsIXPConnectWrappedNative* aParentWN, + WorkerPrivilegeModel aPrivilegeModel) +: mParent(aParent), + mParentWN(aParentWN), + mPrivilegeModel(aPrivilegeModel), + mLock("nsDOMWorker.mLock"), + mInnerScope(nsnull), + mGlobal(NULL), + mNextTimeoutId(0), + mFeatureSuspendDepth(0), + mWrappedNative(nsnull), + mErrorHandlerRecursionCount(0), + mStatus(eRunning), + mExpirationTime(0), + mSuspended(PR_FALSE), + mCompileAttempted(PR_FALSE) +{ +#ifdef DEBUG + PRBool mainThread = NS_IsMainThread(); + NS_ASSERTION(aParent ? !mainThread : mainThread, "Wrong thread!"); +#endif +} + +nsDOMWorker::~nsDOMWorker() +{ + if (mPool) { + mPool->NoteDyingWorker(this); + } + + NS_ASSERTION(!mFeatures.Length(), "Live features!"); + NS_ASSERTION(!mQueuedRunnables.Length(), "Events that never ran!"); + + nsCOMPtr mainThread; + NS_GetMainThread(getter_AddRefs(mainThread)); + + nsIPrincipal* principal; + mPrincipal.forget(&principal); + if (principal) { + NS_ProxyRelease(mainThread, principal, PR_FALSE); + } + + nsIURI* uri; + mBaseURI.forget(&uri); + if (uri) { + NS_ProxyRelease(mainThread, uri, PR_FALSE); + } +} + +// static +nsresult +nsDOMWorker::NewWorker(nsISupports** aNewObject) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + nsCOMPtr newWorker = + NS_ISUPPORTS_CAST(nsIWorker*, new nsDOMWorker(nsnull, nsnull, CONTENT)); + NS_ENSURE_TRUE(newWorker, NS_ERROR_OUT_OF_MEMORY); + + newWorker.forget(aNewObject); + return NS_OK; +} + +// static +nsresult +nsDOMWorker::NewChromeDOMWorker(nsDOMWorker** aNewObject) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + // Subsumes nsContentUtils::IsCallerChrome + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + NS_ASSERTION(ssm, "Should never be null!"); + + PRBool enabled; + nsresult rv = ssm->IsCapabilityEnabled("UniversalXPConnect", &enabled); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(enabled, NS_ERROR_DOM_SECURITY_ERR); + + nsRefPtr newWorker = new nsDOMWorker(nsnull, nsnull, CHROME); + NS_ENSURE_TRUE(newWorker, NS_ERROR_OUT_OF_MEMORY); + + newWorker.forget(aNewObject); + return NS_OK; +} + +// static +nsresult +nsDOMWorker::NewChromeWorker(nsISupports** aNewObject) +{ + nsDOMWorker* newWorker; + nsresult rv = NewChromeDOMWorker(&newWorker); + NS_ENSURE_SUCCESS(rv, rv); + + *aNewObject = NS_ISUPPORTS_CAST(nsIWorker*, newWorker); + return NS_OK; +} + +NS_IMPL_ADDREF_INHERITED(nsDOMWorker, nsDOMWorkerMessageHandler) +NS_IMPL_RELEASE_INHERITED(nsDOMWorker, nsDOMWorkerMessageHandler) + +NS_INTERFACE_MAP_BEGIN(nsDOMWorker) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWorker) + NS_INTERFACE_MAP_ENTRY(nsIClassInfo) + NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) + NS_INTERFACE_MAP_ENTRY(nsIWorker) + NS_INTERFACE_MAP_ENTRY(nsIAbstractWorker) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIDOMEventTarget, + nsDOMWorkerMessageHandler) + NS_INTERFACE_MAP_ENTRY(nsIJSNativeInitializer) + NS_INTERFACE_MAP_ENTRY(nsITimerCallback) +NS_INTERFACE_MAP_END + +// Use the xpc_map_end.h macros to generate the nsIXPCScriptable methods we want +// for the worker. + +#define XPC_MAP_CLASSNAME nsDOMWorker +#define XPC_MAP_QUOTED_CLASSNAME "Worker" +#define XPC_MAP_WANT_PRECREATE +#define XPC_MAP_WANT_POSTCREATE +#define XPC_MAP_WANT_TRACE +#define XPC_MAP_WANT_FINALIZE + +#define XPC_MAP_FLAGS \ + nsIXPCScriptable::USE_JSSTUB_FOR_ADDPROPERTY | \ + nsIXPCScriptable::USE_JSSTUB_FOR_DELPROPERTY | \ + nsIXPCScriptable::USE_JSSTUB_FOR_SETPROPERTY | \ + nsIXPCScriptable::DONT_ENUM_QUERY_INTERFACE | \ + nsIXPCScriptable::CLASSINFO_INTERFACES_ONLY | \ + nsIXPCScriptable::DONT_REFLECT_INTERFACE_NAMES + +#include "xpc_map_end.h" + +NS_IMETHODIMP +nsDOMWorker::PreCreate(nsISupports* aObject, + JSContext* aCx, + JSObject* /* aPlannedParent */, + JSObject** aParent) +{ + nsCOMPtr iworker(do_QueryInterface(aObject)); + NS_ENSURE_TRUE(iworker, NS_ERROR_UNEXPECTED); + + nsCOMPtr wrappedNative; + { + MutexAutoLock lock(mLock); + wrappedNative = mWrappedNative; + } + + // Don't allow XPConnect to create multiple WrappedNatives for this object. + if (wrappedNative) { + JSObject* object; + nsresult rv = wrappedNative->GetJSObject(&object); + NS_ENSURE_SUCCESS(rv, rv); + + *aParent = JS_GetParent(aCx, object); + } + + return IsPrivileged() ? NS_SUCCESS_CHROME_ACCESS_ONLY : NS_OK; +} + +NS_IMETHODIMP +nsDOMWorker::PostCreate(nsIXPConnectWrappedNative* aWrapper, + JSContext* /* aCx */, + JSObject* /* aObj */) +{ + MutexAutoLock lock(mLock); + mWrappedNative = aWrapper; + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorker::Trace(nsIXPConnectWrappedNative* /* aWrapper */, + JSTracer* aTracer, + JSObject* /*aObj */) +{ + PRBool canceled = PR_FALSE; + { + MutexAutoLock lock(mLock); + canceled = mStatus == eKilled; + } + + if (!canceled) { + nsDOMWorkerMessageHandler::Trace(aTracer); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorker::Finalize(nsIXPConnectWrappedNative* /* aWrapper */, + JSContext* aCx, + JSObject* /* aObj */) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + // Don't leave dangling JSObject pointers in our handlers! + ClearAllListeners(); + + // Clear our wrapped native now that it has died. + { + MutexAutoLock lock(mLock); + mWrappedNative = nsnull; + } + + // Do this *after* we null out mWrappedNative so that we don't hand out a + // freed pointer. + if (TerminateInternal(PR_TRUE) == NS_ERROR_ILLEGAL_DURING_SHUTDOWN) { + // We're shutting down, jump right to Kill. + Kill(); + } + + return NS_OK; +} + +NS_IMPL_CI_INTERFACE_GETTER3(nsDOMWorker, nsIWorker, + nsIAbstractWorker, + nsIDOMEventTarget) +NS_IMPL_THREADSAFE_DOM_CI_GETINTERFACES(nsDOMWorker) +NS_IMPL_THREADSAFE_DOM_CI_ALL_THE_REST(nsDOMWorker) + +NS_IMETHODIMP +nsDOMWorker::GetHelperForLanguage(PRUint32 aLanguage, + nsISupports** _retval) +{ + if (aLanguage == nsIProgrammingLanguage::JAVASCRIPT) { + NS_ADDREF(*_retval = NS_ISUPPORTS_CAST(nsIWorker*, this)); + } + else { + *_retval = nsnull; + } + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorker::Initialize(nsISupports* aOwner, + JSContext* aCx, + JSObject* aObj, + PRUint32 aArgc, + jsval* aArgv) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ENSURE_ARG_POINTER(aOwner); + + nsCOMPtr globalObj(do_QueryInterface(aOwner)); + NS_ENSURE_TRUE(globalObj, NS_NOINTERFACE); + + return InitializeInternal(globalObj, aCx, aObj, aArgc, aArgv); +} + +nsresult +nsDOMWorker::InitializeInternal(nsIScriptGlobalObject* aOwner, + JSContext* aCx, + JSObject* aObj, + PRUint32 aArgc, + jsval* aArgv) +{ + NS_ASSERTION(aCx, "Null context!"); + NS_ASSERTION(aObj, "Null global object!"); + + NS_ENSURE_TRUE(aArgc, NS_ERROR_XPC_NOT_ENOUGH_ARGS); + NS_ENSURE_ARG_POINTER(aArgv); + + JSString* str = JS_ValueToString(aCx, aArgv[0]); + NS_ENSURE_TRUE(str, NS_ERROR_XPC_BAD_CONVERT_JS); + + nsDependentJSString depStr; + NS_ENSURE_TRUE(depStr.init(aCx, str), NS_ERROR_OUT_OF_MEMORY); + + mScriptURL.Assign(depStr); + NS_ENSURE_FALSE(mScriptURL.IsEmpty(), NS_ERROR_INVALID_ARG); + + nsresult rv; + + // Figure out the principal and base URI to use if we're on the main thread. + // Otherwise this is a sub-worker and it will have its principal set by the + // script loader. + if (NS_IsMainThread()) { + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + NS_ASSERTION(ssm, "Should never be null!"); + + PRBool isChrome; + rv = ssm->IsCapabilityEnabled("UniversalXPConnect", &isChrome); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ASSERTION(isChrome || aOwner, "How can we have a non-chrome, non-window " + "worker?!"); + + // Chrome callers (whether ChromeWorker of Worker) always get the system + // principal here as they're allowed to load anything. The script loader may + // change the principal later depending on the script uri. + if (isChrome) { + rv = ssm->GetSystemPrincipal(getter_AddRefs(mPrincipal)); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (aOwner) { + // We're being created inside a window. Get the document's base URI and + // use it as our base URI. + nsCOMPtr domWindow = do_QueryInterface(aOwner, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsIDOMDocument* domDocument = domWindow->GetExtantDocument(); + NS_ENSURE_STATE(domDocument); + + nsCOMPtr document = do_QueryInterface(domDocument, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + mBaseURI = document->GetDocBaseURI(); + + if (!mPrincipal) { + // Use the document's NodePrincipal as our principal if we're not being + // called from chrome. + mPrincipal = document->NodePrincipal(); + NS_ENSURE_STATE(mPrincipal); + } + } + else { + // We're being created outside of a window. Need to figure out the script + // that is creating us in order for us to use relative URIs later on. + JSStackFrame* frame = JS_GetScriptedCaller(aCx, nsnull); + if (frame) { + JSScript* script = JS_GetFrameScript(aCx, frame); + NS_ENSURE_STATE(script); + + const char* filename = JS_GetScriptFilename(aCx, script); + + rv = NS_NewURI(getter_AddRefs(mBaseURI), filename); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + NS_ASSERTION(mPrincipal, "Should have set the principal!"); + } + + NS_ASSERTION(!mGlobal, "Already got a global?!"); + + nsCOMPtr thisWrapped; + jsval v; + rv = nsContentUtils::WrapNative(aCx, aObj, static_cast(this), &v, + getter_AddRefs(thisWrapped)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ASSERTION(mWrappedNative, "Post-create hook should have set this!"); + + mKillTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr mainThread; + rv = NS_GetMainThread(getter_AddRefs(mainThread)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mKillTimer->SetTarget(mainThread); + NS_ENSURE_SUCCESS(rv, rv); + + // 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 runnable(new nsWorkerHoldingRunnable(this)); + NS_ENSURE_TRUE(runnable, NS_ERROR_OUT_OF_MEMORY); + + nsRefPtr threadService = + nsDOMThreadService::GetOrInitService(); + NS_ENSURE_STATE(threadService); + + rv = threadService->RegisterWorker(this, aOwner); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ASSERTION(mPool, "RegisterWorker should have set our pool!"); + + rv = threadService->Dispatch(this, runnable); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +void +nsDOMWorker::Cancel() +{ + // Called by the pool when the window that created us is being torn down. Must + // always be on the main thread. + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + // We set the eCanceled status to indicate this. It behaves just like the + // eTerminated status (canceled while close runnable is unscheduled, not + // canceled while close runnable is running) except that it always reports + // that it is canceled when running on the main thread. This status trumps all + // others (except eKilled). Have to do this because the window that created + // us has gone away and had its scope cleared so XPConnect will assert all + // over the place if we try to run anything. + + PRBool enforceTimeout = PR_FALSE; + { + MutexAutoLock lock(mLock); + + NS_ASSERTION(mStatus != eCanceled, "Canceled more than once?!"); + + if (mStatus == eKilled) { + return; + } + + DOMWorkerStatus oldStatus = mStatus; + mStatus = eCanceled; + if (oldStatus != eRunning) { + enforceTimeout = PR_TRUE; + } + } + + PRUint32 timeoutMS = nsDOMThreadService::GetWorkerCloseHandlerTimeoutMS(); + NS_ASSERTION(timeoutMS, "This must not be 0!"); + +#ifdef DEBUG + nsresult rv; +#endif + if (enforceTimeout) { + // Tell the thread service to enforce a timeout on the close handler that + // is already scheduled. + nsDOMThreadService::get()-> + SetWorkerTimeout(this, PR_MillisecondsToInterval(timeoutMS)); + +#ifdef DEBUG + rv = +#endif + mKillTimer->InitWithCallback(this, timeoutMS, nsITimer::TYPE_ONE_SHOT); + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to init kill timer!"); + + return; + } + +#ifdef DEBUG + rv = +#endif + FireCloseRunnable(PR_MillisecondsToInterval(timeoutMS), PR_TRUE, PR_FALSE); + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to fire close runnable!"); +} + +void +nsDOMWorker::Kill() +{ + // Cancel all features and set our status to eKilled. This should only be + // called on the main thread by the thread service or our kill timer to + // indicate that the worker's close handler has run (or timed out). + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(IsClosing(), "Close handler should have run by now!"); + + // If the close handler finished before our kill timer then we don't need it + // any longer. + if (mKillTimer) { + mKillTimer->Cancel(); + mKillTimer = nsnull; + } + + PRUint32 count, index; + nsAutoTArray, 20> features; + { + MutexAutoLock lock(mLock); + + if (mStatus == eKilled) { + NS_ASSERTION(mFeatures.Length() == 0, "Features added after killed!"); + return; + } + mStatus = eKilled; + + count = mFeatures.Length(); + for (index = 0; index < count; index++) { + nsDOMWorkerFeature*& feature = mFeatures[index]; + +#ifdef DEBUG + nsRefPtr* newFeature = +#endif + features.AppendElement(feature); + NS_ASSERTION(newFeature, "Out of memory!"); + + feature->FreeToDie(PR_TRUE); + } + + mFeatures.Clear(); + } + + count = features.Length(); + for (index = 0; index < count; index++) { + features[index]->Cancel(); + } + + // Make sure we kill any queued runnables that we never had a chance to run. + mQueuedRunnables.Clear(); + + // We no longer need to keep our inner scope. + mInnerScope = nsnull; + mScopeWN = nsnull; + mGlobal = NULL; + + // And we can let our parent die now too. + mParent = nsnull; + mParentWN = nsnull; +} + +void +nsDOMWorker::Suspend() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + PRBool shouldSuspendFeatures; + { + MutexAutoLock lock(mLock); + NS_ASSERTION(!mSuspended, "Suspended more than once!"); + shouldSuspendFeatures = !mSuspended; + mSuspended = PR_TRUE; + } + + if (shouldSuspendFeatures) { + SuspendFeatures(); + } +} + +void +nsDOMWorker::Resume() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + PRBool shouldResumeFeatures; + { + MutexAutoLock lock(mLock); +#ifdef DEBUG + // Should only have a mismatch if GC or Cancel happened while suspended. + if (!mSuspended) { + NS_ASSERTION(mStatus == eCanceled || + (mStatus == eTerminated && !mWrappedNative), + "Not suspended!"); + } +#endif + shouldResumeFeatures = mSuspended; + mSuspended = PR_FALSE; + } + + if (shouldResumeFeatures) { + ResumeFeatures(); + } + + // Repost any events that were queued for the main thread while suspended. + PRUint32 count = mQueuedRunnables.Length(); + for (PRUint32 index = 0; index < count; index++) { + NS_DispatchToCurrentThread(mQueuedRunnables[index]); + } + mQueuedRunnables.Clear(); +} + +PRBool +nsDOMWorker::IsCanceled() +{ + MutexAutoLock lock(mLock); + return IsCanceledNoLock(); +} + +PRBool +nsDOMWorker::IsCanceledNoLock() +{ + // If we haven't started the close process then we're not canceled. + if (mStatus == eRunning) { + return PR_FALSE; + } + + // There are several conditions under which we want JS code to abort and all + // other functions to bail: + // 1. If we've already run our close handler then we are canceled forevermore. + // 2. If we've been terminated then we want to pretend to be canceled until + // our close handler is scheduled and running. + // 3. If we've been canceled then we pretend to be canceled until the close + // handler has been scheduled. + // 4. If the close handler has run for longer than the allotted time then we + // should be canceled as well. + // 5. If we're on the main thread then we'll pretend to be canceled if the + // user has navigated away from the page. + return mStatus == eKilled || + (mStatus == eTerminated && !mExpirationTime) || + (mStatus == eCanceled && !mExpirationTime) || + (mExpirationTime && mExpirationTime != PR_INTERVAL_NO_TIMEOUT && + mExpirationTime <= PR_IntervalNow()) || + (mStatus == eCanceled && NS_IsMainThread()); +} + +PRBool +nsDOMWorker::IsClosing() +{ + MutexAutoLock lock(mLock); + return mStatus != eRunning; +} + +PRBool +nsDOMWorker::IsSuspended() +{ + MutexAutoLock lock(mLock); + return mSuspended; +} + +nsresult +nsDOMWorker::PostMessageInternal(PRBool aToInner) +{ + nsIXPConnect* xpc = nsContentUtils::XPConnect(); + NS_ENSURE_TRUE(xpc, NS_ERROR_UNEXPECTED); + + nsAXPCNativeCallContext* cc; + nsresult rv = xpc->GetCurrentNativeCallContext(&cc); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(cc, NS_ERROR_UNEXPECTED); + + PRUint32 argc; + rv = cc->GetArgc(&argc); + NS_ENSURE_SUCCESS(rv, rv); + + if (!argc) { + return NS_ERROR_XPC_NOT_ENOUGH_ARGS; + } + + jsval* argv; + rv = cc->GetArgvPtr(&argv); + NS_ENSURE_SUCCESS(rv, rv); + + JSContext* cx; + rv = cc->GetJSContext(&cx); + NS_ENSURE_SUCCESS(rv, rv); + + // If we're a ChromeWorker then we allow wrapped natives to be passed via + // structured cloning by supplying a custom write callback. To do that we need + // to make sure they stay alive while the message is being sent, so we collect + // the wrapped natives in an array to be packaged with the message. + JSStructuredCloneCallbacks callbacks = { + nsnull, IsPrivileged() ? WriteStructuredClone : nsnull, nsnull + }; + + JSAutoRequest ar(cx); + + JSAutoStructuredCloneBuffer buffer; + nsTArray > wrappedNatives; + if (!buffer.write(cx, argv[0], &callbacks, &wrappedNatives)) { + return NS_ERROR_DOM_DATA_CLONE_ERR; + } + + nsRefPtr message = new nsDOMWorkerMessageEvent(); + NS_ENSURE_TRUE(message, NS_ERROR_OUT_OF_MEMORY); + + rv = message->InitMessageEvent(NS_LITERAL_STRING("message"), PR_FALSE, + PR_FALSE, EmptyString(), EmptyString(), + nsnull); + NS_ENSURE_SUCCESS(rv, rv); + + rv = message->SetJSData(cx, buffer, wrappedNatives); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr runnable = + new nsDOMFireEventRunnable(this, message, aToInner); + NS_ENSURE_TRUE(runnable, NS_ERROR_OUT_OF_MEMORY); + + // If aToInner is true then we want to target the runnable at this worker's + // thread. Otherwise we need to target the parent's thread. + nsDOMWorker* target = aToInner ? this : mParent; + + // If this is a top-level worker then target the main thread. Otherwise use + // the thread service to find the target's thread. + if (!target) { + nsCOMPtr mainThread; + rv = NS_GetMainThread(getter_AddRefs(mainThread)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mainThread->Dispatch(runnable, NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + } + else { + rv = nsDOMThreadService::get()->Dispatch(target, runnable); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +PRBool +nsDOMWorker::SetGlobalForContext(JSContext* aCx, nsLazyAutoRequest *aRequest, + JSAutoEnterCompartment *aComp) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (!CompileGlobalObject(aCx, aRequest, aComp)) { + return PR_FALSE; + } + + JS_SetGlobalObject(aCx, mGlobal); + return PR_TRUE; +} + +PRBool +nsDOMWorker::CompileGlobalObject(JSContext* aCx, nsLazyAutoRequest *aRequest, + JSAutoEnterCompartment *aComp) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + // On success, we enter a request and a cross-compartment call that both + // belong to the caller. But on failure, we must not remain in a request or + // cross-compartment call. So we enter both only locally at first. On + // failure, the local request and call will automatically get cleaned + // up. Once success is certain, we swap them into *aRequest and *aCall. + nsLazyAutoRequest localRequest; + JSAutoEnterCompartment localAutoCompartment; + localRequest.enter(aCx); + + PRBool success; + if (mGlobal) { + success = localAutoCompartment.enter(aCx, mGlobal); + NS_ENSURE_TRUE(success, PR_FALSE); + + aRequest->swap(localRequest); + aComp->swap(localAutoCompartment); + return PR_TRUE; + } + + if (mCompileAttempted) { + // Don't try to recompile a bad script. + return PR_FALSE; + } + mCompileAttempted = PR_TRUE; + + NS_ASSERTION(!mScriptURL.IsEmpty(), "Must have a url here!"); + + NS_ASSERTION(!JS_GetGlobalObject(aCx), "Global object should be unset!"); + + nsRefPtr scope = new nsDOMWorkerScope(this); + NS_ENSURE_TRUE(scope, PR_FALSE); + + nsISupports* scopeSupports = NS_ISUPPORTS_CAST(nsIWorkerScope*, scope); + + nsIXPConnect* xpc = nsContentUtils::XPConnect(); + + const PRUint32 flags = nsIXPConnect::INIT_JS_STANDARD_CLASSES | + nsIXPConnect::OMIT_COMPONENTS_OBJECT; + + nsCOMPtr globalWrapper; + nsresult rv = + xpc->InitClassesWithNewWrappedGlobal(aCx, scopeSupports, + NS_GET_IID(nsISupports), nsnull, + NS_ISUPPORTS_CAST(nsIWorker*, this), + flags, getter_AddRefs(globalWrapper)); + NS_ENSURE_SUCCESS(rv, PR_FALSE); + + JSObject* global; + rv = globalWrapper->GetJSObject(&global); + NS_ENSURE_SUCCESS(rv, PR_FALSE); + + NS_ASSERTION(JS_GetGlobalObject(aCx) == global, "Global object mismatch!"); + + success = localAutoCompartment.enter(aCx, global); + NS_ENSURE_TRUE(success, PR_FALSE); + +#ifdef DEBUG + { + jsval components; + if (JS_GetProperty(aCx, global, "Components", &components)) { + NS_ASSERTION(components == JSVAL_VOID, + "Components property still defined!"); + } + } +#endif + + // Set up worker thread functions. + success = JS_DefineFunctions(aCx, global, gDOMWorkerFunctions); + NS_ENSURE_TRUE(success, PR_FALSE); + + success = JS_DefineProfilingFunctions(aCx, global); + NS_ENSURE_TRUE(success, PR_FALSE); + + if (IsPrivileged()) { + // Add chrome functions. + success = JS_DefineFunctions(aCx, global, gDOMWorkerChromeFunctions); + NS_ENSURE_TRUE(success, PR_FALSE); + + success = JS_DefineProperty(aCx, global, "XPCOM", JSVAL_VOID, + nsDOMWorkerFunctions::XPCOMLazyGetter, nsnull, + 0); + NS_ENSURE_TRUE(success, PR_FALSE); + +#ifdef BUILD_CTYPES + // Add the lazy getter for ctypes. + success = JS_DefineProperty(aCx, global, "ctypes", JSVAL_VOID, + nsDOMWorkerFunctions::CTypesLazyGetter, nsnull, + 0); + NS_ENSURE_TRUE(success, PR_FALSE); +#endif + } + + // From here on out we have to remember to null mGlobal, mInnerScope, and + // mScopeWN if something fails! We really don't need to hang on to mGlobal + // as long as we have mScopeWN, but it saves us a virtual call every time the + // worker is scheduled. Meh. + mGlobal = global; + mInnerScope = scope; + mScopeWN = scope->GetWrappedNative(); + NS_ASSERTION(mScopeWN, "Should have a wrapped native here!"); + + nsRefPtr loader = + new nsDOMWorkerScriptLoader(this); + + rv = AddFeature(loader, aCx); + if (NS_FAILED(rv)) { + mGlobal = NULL; + mInnerScope = nsnull; + mScopeWN = nsnull; + return PR_FALSE; + } + + rv = loader->LoadWorkerScript(aCx, mScriptURL); + + JS_ReportPendingException(aCx); + + if (NS_FAILED(rv)) { + mGlobal = NULL; + mInnerScope = nsnull; + mScopeWN = nsnull; + return PR_FALSE; + } + + NS_ASSERTION(mPrincipal, "Script loader didn't set our principal!"); + NS_ASSERTION(mBaseURI, "Script loader didn't set our base uri!"); + + // Make sure we kept the system principal. + if (IsPrivileged() && !nsContentUtils::IsSystemPrincipal(mPrincipal)) { + static const char warning[] = "ChromeWorker attempted to load a " + "non-chrome worker script!"; + NS_WARNING(warning); + + JS_ReportError(aCx, warning); + + mGlobal = NULL; + mInnerScope = nsnull; + mScopeWN = nsnull; + return PR_FALSE; + } + + rv = loader->ExecuteScripts(aCx); + + JS_ReportPendingException(aCx); + + if (NS_FAILED(rv)) { + mGlobal = NULL; + mInnerScope = nsnull; + mScopeWN = nsnull; + return PR_FALSE; + } + + aRequest->swap(localRequest); + aComp->swap(localAutoCompartment); + return PR_TRUE; +} + +void +nsDOMWorker::SetPool(nsDOMWorkerPool* aPool) +{ + NS_ASSERTION(!mPool, "Shouldn't ever set pool more than once!"); + mPool = aPool; +} + +already_AddRefed +nsDOMWorker::GetWrappedNative() +{ + nsCOMPtr wrappedNative; + { + MutexAutoLock lock(mLock); + wrappedNative = mWrappedNative; + } + return wrappedNative.forget(); +} + +nsresult +nsDOMWorker::AddFeature(nsDOMWorkerFeature* aFeature, + JSContext* aCx) +{ + NS_ASSERTION(aFeature, "Null pointer!"); + + PRBool shouldSuspend; + { + // aCx may be null. + JSAutoSuspendRequest asr(aCx); + + MutexAutoLock lock(mLock); + + if (mStatus == eKilled) { + // No features may be added after we've been canceled. Sorry. + return NS_ERROR_FAILURE; + } + + nsDOMWorkerFeature** newFeature = mFeatures.AppendElement(aFeature); + NS_ENSURE_TRUE(newFeature, NS_ERROR_OUT_OF_MEMORY); + + aFeature->FreeToDie(PR_FALSE); + shouldSuspend = mFeatureSuspendDepth > 0; + } + + if (shouldSuspend) { + aFeature->Suspend(); + } + + return NS_OK; +} + +void +nsDOMWorker::RemoveFeature(nsDOMWorkerFeature* aFeature, + JSContext* aCx) +{ + NS_ASSERTION(aFeature, "Null pointer!"); + + // This *must* be a nsRefPtr so that we call Release after setting FreeToDie. + nsRefPtr feature(aFeature); + { + // aCx may be null. + JSAutoSuspendRequest asr(aCx); + + MutexAutoLock lock(mLock); + +#ifdef DEBUG + PRBool removed = +#endif + mFeatures.RemoveElement(aFeature); + NS_ASSERTION(removed, "Feature not in the list!"); + + aFeature->FreeToDie(PR_TRUE); + } +} + +void +nsDOMWorker::CancelTimeoutWithId(PRUint32 aId) +{ + nsRefPtr foundFeature; + { + MutexAutoLock lock(mLock); + PRUint32 count = mFeatures.Length(); + for (PRUint32 index = 0; index < count; index++) { + nsDOMWorkerFeature*& feature = mFeatures[index]; + if (feature->HasId() && feature->GetId() == aId) { + foundFeature = feature; + feature->FreeToDie(PR_TRUE); + mFeatures.RemoveElementAt(index); + break; + } + } + } + + if (foundFeature) { + foundFeature->Cancel(); + } +} + +void +nsDOMWorker::SuspendFeatures() +{ + nsAutoTArray, 20> features; + { + MutexAutoLock lock(mLock); + + // We don't really have to worry about overflow here because the only way + // to do this is through recursive script loading, which uses the stack. We + // would exceed our stack limit long before this counter. + NS_ASSERTION(mFeatureSuspendDepth < PR_UINT32_MAX, "Shouldn't happen!"); + if (++mFeatureSuspendDepth != 1) { + // Allow nested suspending of timeouts. + return; + } + +#ifdef DEBUG + nsRefPtr* newFeatures = +#endif + features.AppendElements(mFeatures); + NS_WARN_IF_FALSE(newFeatures, "Out of memory!"); + } + + PRUint32 count = features.Length(); + for (PRUint32 i = 0; i < count; i++) { + features[i]->Suspend(); + } +} + +void +nsDOMWorker::ResumeFeatures() +{ + nsAutoTArray, 20> features; + { + MutexAutoLock lock(mLock); + + NS_ASSERTION(mFeatureSuspendDepth > 0, "Shouldn't happen!"); + if (--mFeatureSuspendDepth != 0) { + return; + } + + features.AppendElements(mFeatures); + } + + PRUint32 count = features.Length(); + for (PRUint32 i = 0; i < count; i++) { + features[i]->Resume(); + } +} + +void +nsDOMWorker::SetPrincipal(nsIPrincipal* aPrincipal) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(aPrincipal, "Null pointer!"); + + mPrincipal = aPrincipal; +} + +nsresult +nsDOMWorker::SetBaseURI(nsIURI* aURI) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(aURI, "Don't hand me a null pointer!"); + + mBaseURI = aURI; + + nsCOMPtr url(do_QueryInterface(aURI)); + NS_ENSURE_TRUE(url, NS_ERROR_NO_INTERFACE); + + mLocation = nsDOMWorkerLocation::NewLocation(url); + NS_ENSURE_TRUE(mLocation, NS_ERROR_FAILURE); + + return NS_OK; +} + +void +nsDOMWorker::ClearBaseURI() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + mBaseURI = nsnull; + mLocation = nsnull; +} + +nsresult +nsDOMWorker::FireCloseRunnable(PRIntervalTime aTimeoutInterval, + PRBool aClearQueue, + PRBool aFromFinalize) +{ + // Resume the worker (but not its features) if we're currently suspended. This + // should only ever happen if we are being called from Cancel (page falling + // out of bfcache or quitting) or Finalize, in which case all we really want + // to do is unblock the waiting thread. + PRBool wakeUp; + { + MutexAutoLock lock(mLock); + NS_ASSERTION(mExpirationTime == 0, + "Close runnable should not be scheduled already!"); + + if ((wakeUp = mSuspended)) { + NS_ASSERTION(mStatus == eCanceled || + (mStatus == eTerminated && aFromFinalize), + "How can this happen otherwise?!"); + mSuspended = PR_FALSE; + } + } + + if (wakeUp) { + ReentrantMonitorAutoEnter mon(mPool->GetReentrantMonitor()); + mon.NotifyAll(); + } + + nsRefPtr event = new nsDOMWorkerEvent(); + NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv = + event->InitEvent(NS_LITERAL_STRING("close"), PR_FALSE, PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr runnable = + new nsDOMFireEventRunnable(this, event, PR_TRUE); + NS_ENSURE_TRUE(runnable, NS_ERROR_OUT_OF_MEMORY); + + // Our worker has been collected and we want to keep the inner scope alive, + // so pass that along in the runnable. + if (aFromFinalize) { + // Make sure that our scope wrapped native exists here, but if the worker + // script failed to compile then it will be null already. + if (mGlobal) { + NS_ASSERTION(mScopeWN, "This shouldn't be null!"); + } + runnable->ReplaceWrappedNative(mScopeWN); + } + + return nsDOMThreadService::get()->Dispatch(this, runnable, aTimeoutInterval, + aClearQueue); +} + +nsresult +nsDOMWorker::Close() +{ + { + MutexAutoLock lock(mLock); + NS_ASSERTION(mStatus != eKilled, "This should be impossible!"); + if (mStatus != eRunning) { + return NS_OK; + } + mStatus = eClosed; + } + + nsresult rv = FireCloseRunnable(PR_INTERVAL_NO_TIMEOUT, PR_FALSE, PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +nsDOMWorker::TerminateInternal(PRBool aFromFinalize) +{ + { + MutexAutoLock lock(mLock); +#ifdef DEBUG + if (!aFromFinalize) { + NS_ASSERTION(mStatus != eCanceled, "Shouldn't be able to get here!"); + } +#endif + + if (mStatus == eRunning) { + // This is the beginning of the close process, fire an event and prevent + // any other close events from being generated. + mStatus = eTerminated; + } + else { + if (mStatus == eClosed) { + // The worker was previously closed which means that an expiration time + // might not be set. Setting the status to eTerminated will force the + // worker to jump to its close handler. + mStatus = eTerminated; + } + // No need to fire another close handler, it has already been done. + return NS_OK; + } + } + + nsresult rv = FireCloseRunnable(PR_INTERVAL_NO_TIMEOUT, PR_TRUE, + aFromFinalize); + if (rv == NS_ERROR_ILLEGAL_DURING_SHUTDOWN) { + return rv; + } + + // Warn about other kinds of failures. + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +already_AddRefed +nsDOMWorker::GetParent() +{ + nsRefPtr parent(mParent); + return parent.forget(); +} + +void +nsDOMWorker::SetExpirationTime(PRIntervalTime aExpirationTime) +{ + { + MutexAutoLock lock(mLock); + + NS_ASSERTION(mStatus != eRunning && mStatus != eKilled, "Bad status!"); + NS_ASSERTION(!mExpirationTime || mExpirationTime == PR_INTERVAL_NO_TIMEOUT, + "Overwriting a timeout that was previously set!"); + + mExpirationTime = aExpirationTime; + } +} + +#ifdef DEBUG +PRIntervalTime +nsDOMWorker::GetExpirationTime() +{ + MutexAutoLock lock(mLock); + return mExpirationTime; +} +#endif + +// static +JSObject* +nsDOMWorker::ReadStructuredClone(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32 aTag, + uint32 aData, + void* aClosure) +{ + NS_ASSERTION(aCx, "Null context!"); + NS_ASSERTION(aReader, "Null reader!"); + NS_ASSERTION(!aClosure, "Shouldn't have a closure here!"); + + if (aTag == DOMWORKER_SCTAG_WRAPPEDNATIVE) { + NS_ASSERTION(!aData, "Huh?"); + + nsISupports* wrappedNative; + if (JS_ReadBytes(aReader, &wrappedNative, sizeof(wrappedNative))) { + NS_ASSERTION(wrappedNative, "Null pointer?!"); + + JSObject* global = JS_GetGlobalForObject(aCx, JS_GetScopeChain(aCx)); + if (global) { + jsval val; + nsCOMPtr wrapper; + if (NS_SUCCEEDED(nsContentUtils::WrapNative(aCx, global, wrappedNative, + &val, + getter_AddRefs(wrapper)))) { + return JSVAL_TO_OBJECT(val); + } + } + } + } + + // Something failed above, try using the runtime callbacks instead. + const JSStructuredCloneCallbacks* runtimeCallbacks = + aCx->runtime->structuredCloneCallbacks; + if (runtimeCallbacks) { + return runtimeCallbacks->read(aCx, aReader, aTag, aData, nsnull); + } + + // We can't handle this object, throw an exception if one hasn't been thrown + // already. + if (!JS_IsExceptionPending(aCx)) { + nsDOMClassInfo::ThrowJSException(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); + } + return nsnull; +} + +PRBool +nsDOMWorker::QueueSuspendedRunnable(nsIRunnable* aRunnable) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + return mQueuedRunnables.AppendElement(aRunnable) ? PR_TRUE : PR_FALSE; +} + +NS_IMETHODIMP +nsDOMWorker::RemoveEventListener(const nsAString& aType, + nsIDOMEventListener* aListener, + PRBool aUseCapture) +{ + if (IsCanceled()) { + return NS_OK; + } + + return nsDOMWorkerMessageHandler::RemoveEventListener(aType, aListener, + aUseCapture); +} + +NS_IMETHODIMP +nsDOMWorker::DispatchEvent(nsIDOMEvent* aEvent, + PRBool* _retval) +{ + { + MutexAutoLock lock(mLock); + if (IsCanceledNoLock()) { + return NS_OK; + } + if (mStatus == eTerminated) { + nsCOMPtr messageEvent(do_QueryInterface(aEvent)); + if (messageEvent) { + // This is a message event targeted to a terminated worker. Ignore it. + return NS_OK; + } + } + } + + return nsDOMWorkerMessageHandler::DispatchEvent(aEvent, _retval); +} + +NS_IMETHODIMP +nsDOMWorker::AddEventListener(const nsAString& aType, + nsIDOMEventListener* aListener, + PRBool aUseCapture, + PRBool aWantsUntrusted, + PRUint8 aOptionalArgc) +{ + NS_ASSERTION(mWrappedNative, "Called after Finalize!"); + if (IsCanceled()) { + return NS_OK; + } + + return nsDOMWorkerMessageHandler::AddEventListener(aType, aListener, + aUseCapture, + aWantsUntrusted, + aOptionalArgc); +} + +/** + * See nsIWorker + */ +NS_IMETHODIMP +nsDOMWorker::PostMessage(/* JSObject aMessage */) +{ + { + MutexAutoLock lock(mLock); + // There's no reason to dispatch this message after the close handler has + // been triggered since it will never be allowed to run. + if (mStatus != eRunning) { + return NS_OK; + } + } + + return PostMessageInternal(PR_TRUE); +} + +/** + * See nsIWorker + */ +NS_IMETHODIMP +nsDOMWorker::GetOnerror(nsIDOMEventListener** aOnerror) +{ + NS_ENSURE_ARG_POINTER(aOnerror); + + if (IsCanceled()) { + *aOnerror = nsnull; + return NS_OK; + } + + nsCOMPtr listener = + GetOnXListener(NS_LITERAL_STRING("error")); + + listener.forget(aOnerror); + return NS_OK; +} + +/** + * See nsIWorker + */ +NS_IMETHODIMP +nsDOMWorker::SetOnerror(nsIDOMEventListener* aOnerror) +{ + NS_ASSERTION(mWrappedNative, "Called after Finalize!"); + if (IsCanceled()) { + return NS_OK; + } + + return SetOnXListener(NS_LITERAL_STRING("error"), aOnerror); +} + +/** + * See nsIWorker + */ +NS_IMETHODIMP +nsDOMWorker::GetOnmessage(nsIDOMEventListener** aOnmessage) +{ + NS_ENSURE_ARG_POINTER(aOnmessage); + + if (IsCanceled()) { + *aOnmessage = nsnull; + return NS_OK; + } + + nsCOMPtr listener = + GetOnXListener(NS_LITERAL_STRING("message")); + + listener.forget(aOnmessage); + return NS_OK; +} + +/** + * See nsIWorker + */ +NS_IMETHODIMP +nsDOMWorker::SetOnmessage(nsIDOMEventListener* aOnmessage) +{ + NS_ASSERTION(mWrappedNative, "Called after Finalize!"); + if (IsCanceled()) { + return NS_OK; + } + + return SetOnXListener(NS_LITERAL_STRING("message"), aOnmessage); +} + +NS_IMETHODIMP +nsDOMWorker::Terminate() +{ + return TerminateInternal(PR_FALSE); +} + +NS_IMETHODIMP +nsDOMWorker::Notify(nsITimer* aTimer) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + Kill(); + return NS_OK; +} + +NS_IMETHODIMP +nsWorkerFactory::NewChromeWorker(nsIWorker** _retval) +{ + nsresult rv; + + // Get the arguments from XPConnect. + nsCOMPtr xpc; + xpc = do_GetService(nsIXPConnect::GetCID()); + NS_ASSERTION(xpc, "Could not get XPConnect"); + + nsAXPCNativeCallContext* cc; + rv = xpc->GetCurrentNativeCallContext(&cc); + NS_ENSURE_SUCCESS(rv, rv); + + JSContext* cx; + rv = cc->GetJSContext(&cx); + NS_ENSURE_SUCCESS(rv, rv); + + PRUint32 argc; + rv = cc->GetArgc(&argc); + NS_ENSURE_SUCCESS(rv, rv); + + jsval* argv; + rv = cc->GetArgvPtr(&argv); + NS_ENSURE_SUCCESS(rv, rv); + + // Determine the current script global. We need it to register the worker. + // NewChromeDOMWorker will check that we are chrome, so no access check. + JSObject* global = JS_GetGlobalForScopeChain(cx); + NS_ENSURE_TRUE(global, NS_ERROR_UNEXPECTED); + + // May be null if we're being called from a JSM or something. + nsCOMPtr scriptGlobal = + nsJSUtils::GetStaticScriptGlobal(cx, global); + + // Create, initialize, and return the worker. + nsRefPtr chromeWorker; + rv = nsDOMWorker::NewChromeDOMWorker(getter_AddRefs(chromeWorker)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = chromeWorker->InitializeInternal(scriptGlobal, cx, global, argc, argv); + NS_ENSURE_SUCCESS(rv, rv); + + chromeWorker.forget(_retval); + return NS_OK; +} + +NS_IMPL_ISUPPORTS1(nsWorkerFactory, nsIWorkerFactory) diff --git a/dom/src/threads/nsDOMWorker.h b/dom/src/threads/nsDOMWorker.h new file mode 100644 index 000000000000..890fb9a52743 --- /dev/null +++ b/dom/src/threads/nsDOMWorker.h @@ -0,0 +1,471 @@ +/* -*- Mode: c++; c-basic-offset: 2; 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 Web Workers. + * + * 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 (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 __NSDOMWORKER_H__ +#define __NSDOMWORKER_H__ + +#include "nsIDOMEventTarget.h" +#include "nsIDOMWorkers.h" +#include "nsIJSNativeInitializer.h" +#include "nsIPrincipal.h" +#include "nsITimer.h" +#include "nsIURI.h" +#include "nsIXPCScriptable.h" + +#include "jsapi.h" +#include "mozilla/Mutex.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsTPtrArray.h" + +#include "nsDOMWorkerMessageHandler.h" + +// {1295EFB5-8644-42B2-8B8E-80EEF56E4284} +#define NS_WORKERFACTORY_CID \ + {0x1295efb5, 0x8644, 0x42b2, \ + {0x8b, 0x8e, 0x80, 0xee, 0xf5, 0x6e, 0x42, 0x84} } + +class nsDOMWorker; +class nsDOMWorkerFeature; +class nsDOMWorkerMessageHandler; +class nsDOMWorkerNavigator; +class nsDOMWorkerPool; +class nsDOMWorkerTimeout; +class nsICancelable; +class nsIDOMEventListener; +class nsIEventTarget; +class nsIRunnable; +class nsIScriptGlobalObject; +class nsIXPConnectWrappedNative; + +class nsDOMWorkerScope : public nsDOMWorkerMessageHandler, + public nsIWorkerScope, + public nsIXPCScriptable +{ + friend class nsDOMWorker; + +public: + NS_DECL_ISUPPORTS_INHERITED + + // nsIDOMEventHandler + NS_FORWARD_INTERNAL_NSIDOMEVENTTARGET(nsDOMWorkerMessageHandler::) + NS_IMETHOD AddEventListener(const nsAString& aType, + nsIDOMEventListener* aListener, + PRBool aUseCapture, + PRBool aWantsUntrusted, + PRUint8 optional_argc); + NS_IMETHOD RemoveEventListener(const nsAString& aType, + nsIDOMEventListener* aListener, + PRBool aUseCapture); + NS_IMETHOD DispatchEvent(nsIDOMEvent* aEvent, + PRBool* _retval); + NS_DECL_NSIWORKERGLOBALSCOPE + NS_DECL_NSIWORKERSCOPE + NS_DECL_NSIXPCSCRIPTABLE + NS_DECL_NSICLASSINFO + + typedef NS_STDCALL_FUNCPROTO(nsresult, SetListenerFunc, nsDOMWorkerScope, + SetOnmessage, (nsIDOMEventListener*)); + + nsDOMWorkerScope(nsDOMWorker* aWorker); + +protected: + already_AddRefed GetWrappedNative(); + +private: + nsDOMWorker* mWorker; + nsIXPConnectWrappedNative* mWrappedNative; + + nsRefPtr mNavigator; + + PRPackedBool mHasOnerror; +}; + +class nsLazyAutoRequest +{ +public: + nsLazyAutoRequest() : mCx(nsnull) {} + + ~nsLazyAutoRequest() { + if (mCx) + JS_EndRequest(mCx); + } + + void enter(JSContext *aCx) { + JS_BeginRequest(aCx); + mCx = aCx; + } + + bool entered() const { return mCx != nsnull; } + + void swap(nsLazyAutoRequest &other) { + JSContext *tmp = mCx; + mCx = other.mCx; + other.mCx = tmp; + } + +private: + JSContext *mCx; +}; + +class nsDOMWorker : public nsDOMWorkerMessageHandler, + public nsIWorker, + public nsITimerCallback, + public nsIJSNativeInitializer, + public nsIXPCScriptable +{ + typedef mozilla::Mutex Mutex; + + friend class nsDOMWorkerFeature; + friend class nsDOMWorkerFunctions; + friend class nsDOMWorkerScope; + friend class nsDOMWorkerScriptLoader; + friend class nsDOMWorkerTimeout; + friend class nsDOMWorkerXHR; + friend class nsDOMWorkerXHRProxy; + friend class nsReportErrorRunnable; + friend class nsDOMFireEventRunnable; + + friend JSBool DOMWorkerOperationCallback(JSContext* aCx); + friend void DOMWorkerErrorReporter(JSContext* aCx, + const char* aMessage, + JSErrorReport* aReport); + +public: + NS_DECL_ISUPPORTS_INHERITED + + // nsIDOMEventHandler + NS_FORWARD_INTERNAL_NSIDOMEVENTTARGET(nsDOMWorkerMessageHandler::) + NS_IMETHOD AddEventListener(const nsAString& aType, + nsIDOMEventListener* aListener, + PRBool aUseCapture, + PRBool aWantsUntrusted, + PRUint8 optional_argc); + NS_IMETHOD RemoveEventListener(const nsAString& aType, + nsIDOMEventListener* aListener, + PRBool aUseCapture); + NS_IMETHOD DispatchEvent(nsIDOMEvent* aEvent, + PRBool* _retval); + + NS_DECL_NSIABSTRACTWORKER + NS_DECL_NSIWORKER + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSICLASSINFO + NS_DECL_NSIXPCSCRIPTABLE + + static nsresult NewWorker(nsISupports** aNewObject); + static nsresult NewChromeWorker(nsISupports** aNewObject); + static nsresult NewChromeDOMWorker(nsDOMWorker** aNewObject); + + enum WorkerPrivilegeModel { CONTENT, CHROME }; + + nsDOMWorker(nsDOMWorker* aParent, + nsIXPConnectWrappedNative* aParentWN, + WorkerPrivilegeModel aModel); + + NS_IMETHOD Initialize(nsISupports* aOwner, + JSContext* aCx, + JSObject* aObj, + PRUint32 aArgc, + jsval* aArgv); + + nsresult InitializeInternal(nsIScriptGlobalObject* aOwner, + JSContext* aCx, + JSObject* aObj, + PRUint32 aArgc, + jsval* aArgv); + + void Cancel(); + void Kill(); + void Suspend(); + void Resume(); + + // This just calls IsCanceledNoLock with an autolock around the call. + PRBool IsCanceled(); + + PRBool IsClosing(); + PRBool IsSuspended(); + + PRBool SetGlobalForContext(JSContext* aCx, nsLazyAutoRequest *aRequest, JSAutoEnterCompartment *aComp); + + void SetPool(nsDOMWorkerPool* aPool); + + nsDOMWorkerPool* Pool() { + return mPool; + } + + Mutex& GetLock() { + return mLock; + } + + already_AddRefed GetWrappedNative(); + already_AddRefed GetParent(); + + nsDOMWorkerScope* GetInnerScope() { + return mInnerScope; + } + + void SetExpirationTime(PRIntervalTime aExpirationTime); +#ifdef DEBUG + PRIntervalTime GetExpirationTime(); +#endif + + PRBool IsPrivileged() { + return mPrivilegeModel == CHROME; + } + + static JSObject* ReadStructuredClone(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32 aTag, + uint32 aData, + void* aClosure); + + /** + * Use this chart to help figure out behavior during each of the closing + * statuses. Details below. + * + * +=============+=============+=================+=======================+ + * | status | clear queue | abort execution | close handler timeout | + * +=============+=============+=================+=======================+ + * | eClosed | yes | no | no | + * +-------------+-------------+-----------------+-----------------------+ + * | eTerminated | yes | yes | no | + * +-------------+-------------+-----------------+-----------------------+ + * | eCanceled | yes | yes | yes | + * +-------------+-------------+-----------------+-----------------------+ + * + */ + + enum DOMWorkerStatus { + // This status means that the close handler has not yet been scheduled. + eRunning = 0, + + // Inner script called Close() on the worker global scope. Setting this + // status causes the worker to clear its queue of events but does not abort + // the currently running script. The close handler is also scheduled with + // no expiration time. This status may be superseded by 'eTerminated' in + // which case the currently running script will be aborted as detailed + // below. It may also be superseded by 'eCanceled' at which point the close + // handler will be assigned an expiration time. Once the close handler has + // completed or timed out the status will be changed to 'eKilled'. + eClosed, + + // Outer script called Terminate() on the worker or the worker object was + // garbage collected in its outer script. Setting this status causes the + // worker to abort immediately, clear its queue of events, and schedules the + // close handler with no expiration time. This status may be superseded by + // 'eCanceled' at which point the close handler will have an expiration time + // assigned. Once the close handler has completed or timed out the status + // will be changed to 'eKilled'. + eTerminated, + + // Either the user navigated away from the owning page, the owning page fell + // out of bfcache, or the user quit the application. Setting this status + // causes the worker to abort immediately and schedules the close handler + // with an expiration time. Since the page has gone away the worker may not + // post any messages. Once the close handler has completed or timed out the + // status will be changed to 'eKilled'. + eCanceled, + + // The close handler has run and the worker is effectively dead. + eKilled + }; + +private: + ~nsDOMWorker(); + + nsresult PostMessageInternal(PRBool aToInner); + + PRBool CompileGlobalObject(JSContext* aCx, nsLazyAutoRequest *aRequest, JSAutoEnterCompartment *aComp); + + PRUint32 NextTimeoutId() { + return ++mNextTimeoutId; + } + + nsresult AddFeature(nsDOMWorkerFeature* aFeature, + JSContext* aCx); + void RemoveFeature(nsDOMWorkerFeature* aFeature, + JSContext* aCx); + void CancelTimeoutWithId(PRUint32 aId); + void SuspendFeatures(); + void ResumeFeatures(); + + nsIPrincipal* GetPrincipal() { + return mPrincipal; + } + + void SetPrincipal(nsIPrincipal* aPrincipal); + + nsIURI* GetBaseURI() { + return mBaseURI; + } + + nsresult SetBaseURI(nsIURI* aURI); + + void ClearBaseURI(); + + nsresult FireCloseRunnable(PRIntervalTime aTimeoutInterval, + PRBool aClearQueue, + PRBool aFromFinalize); + nsresult Close(); + + nsresult TerminateInternal(PRBool aFromFinalize); + + nsIWorkerLocation* GetLocation() { + return mLocation; + } + + PRBool QueueSuspendedRunnable(nsIRunnable* aRunnable); + + // Determines if the worker should be considered "canceled". See the large + // comment in the implementation for more details. + PRBool IsCanceledNoLock(); + +private: + + // mParent will live as long as mParentWN but only mParentWN will keep the JS + // reflection alive, so we only hold one strong reference to mParentWN. + nsDOMWorker* mParent; + nsCOMPtr mParentWN; + + // Whether or not this worker has chrome privileges. Never changed after the + // worker is created. + WorkerPrivilegeModel mPrivilegeModel; + + Mutex mLock; + + nsRefPtr mPool; + + nsDOMWorkerScope* mInnerScope; + nsCOMPtr mScopeWN; + JSObject* mGlobal; + + PRUint32 mNextTimeoutId; + + nsTArray mFeatures; + PRUint32 mFeatureSuspendDepth; + + nsString mScriptURL; + + nsIXPConnectWrappedNative* mWrappedNative; + + nsCOMPtr mPrincipal; + nsCOMPtr mBaseURI; + + PRInt32 mErrorHandlerRecursionCount; + + // Always protected by mLock + DOMWorkerStatus mStatus; + + // Always protected by mLock + PRIntervalTime mExpirationTime; + + nsCOMPtr mKillTimer; + + nsCOMPtr mLocation; + + nsTArray > mQueuedRunnables; + + PRPackedBool mSuspended; + PRPackedBool mCompileAttempted; +}; + +/** + * A worker "feature" holds the worker alive yet can be canceled, paused, and + * resumed by the worker. It is up to each derived class to implement these + * methods. This class uses a custom implementation of Release in order to + * ensure no races between Cancel and object destruction can occur, so derived + * classes must use the ISUPPORTS_INHERITED macros. + * + * To use this class you should inherit it and use the ISUPPORTS_INHERITED + * macros. Then add or remove an instance to the worker using the + * AddFeature/RemoveFeature functions. + */ +class nsDOMWorkerFeature : public nsISupports +{ + friend class nsDOMWorker; + +public: + NS_DECL_ISUPPORTS + + nsDOMWorkerFeature(nsDOMWorker* aWorker) + : mWorker(aWorker), mWorkerWN(aWorker->GetWrappedNative()), mId(0), + mHasId(PR_FALSE), mFreeToDie(PR_TRUE) { } + + nsDOMWorkerFeature(nsDOMWorker* aWorker, PRUint32 aId) + : mWorker(aWorker), mWorkerWN(aWorker->GetWrappedNative()), mId(aId), + mHasId(PR_TRUE), mFreeToDie(PR_TRUE) { } + + virtual void Cancel() = 0; + virtual void Suspend() { } + virtual void Resume() { } + + PRUint32 GetId() { + return mId; + } + + PRBool HasId() { + return mHasId; + } + +protected: + virtual ~nsDOMWorkerFeature() { } + +private: + void FreeToDie(PRBool aFreeToDie) { + mFreeToDie = aFreeToDie; + } + +protected: + nsRefPtr mWorker; + nsCOMPtr mWorkerWN; + PRUint32 mId; + +private: + PRPackedBool mHasId; + PRPackedBool mFreeToDie; +}; + +class nsWorkerFactory : public nsIWorkerFactory +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIWORKERFACTORY +}; + +#endif /* __NSDOMWORKER_H__ */ diff --git a/dom/src/threads/nsDOMWorkerEvents.cpp b/dom/src/threads/nsDOMWorkerEvents.cpp new file mode 100644 index 000000000000..188592edd9c8 --- /dev/null +++ b/dom/src/threads/nsDOMWorkerEvents.cpp @@ -0,0 +1,635 @@ +/* -*- 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 Web Workers. + * + * 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 (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 "nsDOMWorkerEvents.h" + +#include "nsIXMLHttpRequest.h" +#include "nsIXPConnect.h" + +#include "jsapi.h" +#include "nsAXPCNativeCallContext.h" +#include "nsContentUtils.h" +#include "nsThreadUtils.h" + +#include "nsDOMWorkerMessageHandler.h" +#include "nsDOMThreadService.h" +#include "nsDOMWorkerXHR.h" +#include "nsDOMWorkerXHRProxy.h" + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIDOMWorkerPrivateEvent, + NS_IDOMWORKERPRIVATEEVENT_IID) + +nsDOMWorkerPrivateEvent::nsDOMWorkerPrivateEvent(nsIDOMEvent* aEvent) +: mEvent(aEvent), + mProgressEvent(do_QueryInterface(aEvent)), + mMessageEvent(do_QueryInterface(aEvent)), + mErrorEvent(do_QueryInterface(aEvent)), + mPreventDefaultCalled(PR_FALSE) +{ + NS_ASSERTION(aEvent, "Null pointer!"); +} + +NS_IMPL_THREADSAFE_ADDREF(nsDOMWorkerPrivateEvent) +NS_IMPL_THREADSAFE_RELEASE(nsDOMWorkerPrivateEvent) + +NS_INTERFACE_MAP_BEGIN(nsDOMWorkerPrivateEvent) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMWorkerPrivateEvent) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIDOMEvent, nsIDOMWorkerPrivateEvent) + NS_INTERFACE_MAP_ENTRY(nsIDOMWorkerPrivateEvent) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIDOMProgressEvent, mProgressEvent) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIWorkerMessageEvent, mMessageEvent) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIWorkerErrorEvent, mErrorEvent) + NS_INTERFACE_MAP_ENTRY(nsIClassInfo) +NS_INTERFACE_MAP_END + +NS_IMPL_CI_INTERFACE_GETTER1(nsDOMWorkerPrivateEvent, nsIDOMEvent) + +NS_IMPL_THREADSAFE_DOM_CI_HELPER(nsDOMWorkerPrivateEvent) +NS_IMPL_THREADSAFE_DOM_CI_ALL_THE_REST(nsDOMWorkerPrivateEvent) + +NS_IMETHODIMP +nsDOMWorkerPrivateEvent::GetInterfaces(PRUint32* aCount, nsIID*** aArray) +{ + nsCOMPtr ci(do_QueryInterface(mEvent)); + if (ci) { + return ci->GetInterfaces(aCount, aArray); + } + return NS_CI_INTERFACE_GETTER_NAME(nsDOMWorkerPrivateEvent)(aCount, aArray); +} + +NS_IMETHODIMP +nsDOMWorkerPrivateEvent::PreventDefault() +{ + PRBool cancelable = PR_FALSE; + mEvent->GetCancelable(&cancelable); + + if (cancelable) { + mPreventDefaultCalled = PR_TRUE; + } + + return mEvent->PreventDefault(); +} + +NS_IMETHODIMP +nsDOMWorkerPrivateEvent::GetDefaultPrevented(PRBool* aRetVal) +{ + *aRetVal = mPreventDefaultCalled; + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerPrivateEvent::InitEvent(const nsAString& aEventType, + PRBool aCanBubble, + PRBool aCancelable) +{ + mPreventDefaultCalled = PR_FALSE; + return mEvent->InitEvent(aEventType, aCanBubble, aCancelable); +} + +NS_IMETHODIMP +nsDOMWorkerPrivateEvent::InitProgressEvent(const nsAString& aTypeArg, + PRBool aCanBubbleArg, + PRBool aCancelableArg, + PRBool aLengthComputableArg, + PRUint64 aLoadedArg, + PRUint64 aTotalArg) +{ + NS_ASSERTION(mProgressEvent, "Impossible!"); + + mPreventDefaultCalled = PR_FALSE; + return mProgressEvent->InitProgressEvent(aTypeArg, aCanBubbleArg, + aCancelableArg, aLengthComputableArg, + aLoadedArg, aTotalArg); +} + +NS_IMETHODIMP +nsDOMWorkerPrivateEvent::InitMessageEvent(const nsAString& aTypeArg, + PRBool aCanBubbleArg, + PRBool aCancelableArg, + const nsAString& aDataArg, + const nsAString& aOriginArg, + nsISupports* aSourceArg) +{ + NS_ASSERTION(mMessageEvent, "Impossible!"); + + mPreventDefaultCalled = PR_FALSE; + return mMessageEvent->InitMessageEvent(aTypeArg, aCanBubbleArg, + aCancelableArg, aDataArg, aOriginArg, + aSourceArg); +} + +NS_IMETHODIMP +nsDOMWorkerPrivateEvent::InitErrorEvent(const nsAString& aTypeArg, + PRBool aCanBubbleArg, + PRBool aCancelableArg, + const nsAString& aMessageArg, + const nsAString& aFilenameArg, + PRUint32 aLinenoArg) +{ + NS_ASSERTION(mErrorEvent, "Impossible!"); + + mPreventDefaultCalled = PR_FALSE; + return mErrorEvent->InitErrorEvent(aTypeArg, aCanBubbleArg, aCancelableArg, + aMessageArg, aFilenameArg, aLinenoArg); +} + +PRBool +nsDOMWorkerPrivateEvent::PreventDefaultCalled() +{ + return mPreventDefaultCalled; +} + +NS_IMPL_THREADSAFE_ISUPPORTS2(nsDOMWorkerEvent, nsIDOMEvent, + nsIClassInfo) + +NS_IMPL_CI_INTERFACE_GETTER1(nsDOMWorkerEvent, nsIDOMEvent) + +NS_IMPL_THREADSAFE_DOM_CI(nsDOMWorkerEvent) + +NS_IMETHODIMP +nsDOMWorkerEvent::GetType(nsAString& aType) +{ + aType.Assign(mType); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerEvent::GetTarget(nsIDOMEventTarget** aTarget) +{ + NS_ENSURE_ARG_POINTER(aTarget); + NS_IF_ADDREF(*aTarget = mTarget); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerEvent::GetCurrentTarget(nsIDOMEventTarget** aCurrentTarget) +{ + NS_ENSURE_ARG_POINTER(aCurrentTarget); + NS_IF_ADDREF(*aCurrentTarget = mTarget); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerEvent::GetEventPhase(PRUint16* aEventPhase) +{ + NS_ENSURE_ARG_POINTER(aEventPhase); + *aEventPhase = mEventPhase; + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerEvent::GetBubbles(PRBool* aBubbles) +{ + NS_ENSURE_ARG_POINTER(aBubbles); + *aBubbles = mBubbles; + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerEvent::GetCancelable(PRBool* aCancelable) +{ + NS_ENSURE_ARG_POINTER(aCancelable); + *aCancelable = mCancelable; + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerEvent::GetTimeStamp(DOMTimeStamp* aTimeStamp) +{ + NS_ENSURE_ARG_POINTER(aTimeStamp); + *aTimeStamp = mTimeStamp; + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerEvent::StopPropagation() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDOMWorkerEvent::PreventDefault() +{ + mPreventDefaultCalled = PR_TRUE; + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerEvent::GetDefaultPrevented(PRBool* aRetVal) +{ + *aRetVal = mPreventDefaultCalled; + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerEvent::InitEvent(const nsAString& aEventTypeArg, + PRBool aCanBubbleArg, + PRBool aCancelableArg) +{ + NS_ENSURE_FALSE(aEventTypeArg.IsEmpty(), NS_ERROR_INVALID_ARG); + + mType.Assign(aEventTypeArg); + mBubbles = aCanBubbleArg; + mCancelable = aCancelableArg; + mPreventDefaultCalled = PR_FALSE; + mTimeStamp = PR_Now(); + return NS_OK; +} + +nsDOMWorkerMessageEvent::~nsDOMWorkerMessageEvent() +{ + if (mData) { + JSContext* cx = nsDOMThreadService::GetCurrentContext(); + if (cx) { + JS_free(cx, mData); + } + else { + NS_WARNING("Failed to get safe JSContext, leaking event data!"); + } + } +} + +NS_IMPL_ISUPPORTS_INHERITED1(nsDOMWorkerMessageEvent, nsDOMWorkerEvent, + nsIWorkerMessageEvent) + +NS_IMPL_CI_INTERFACE_GETTER2(nsDOMWorkerMessageEvent, nsIDOMEvent, + nsIWorkerMessageEvent) + +NS_IMPL_THREADSAFE_DOM_CI_GETINTERFACES(nsDOMWorkerMessageEvent) + +nsresult +nsDOMWorkerMessageEvent::SetJSData( + JSContext* aCx, + JSAutoStructuredCloneBuffer& aBuffer, + nsTArray >& aWrappedNatives) +{ + NS_ASSERTION(aCx, "Null context!"); + + if (!mDataVal.Hold(aCx)) { + NS_WARNING("Failed to hold jsval!"); + return NS_ERROR_FAILURE; + } + + if (!mWrappedNatives.SwapElements(aWrappedNatives)) { + NS_ERROR("This should never fail!"); + } + + aBuffer.steal(&mData, &mDataLen); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerMessageEvent::GetData(nsAString& aData) +{ + nsIXPConnect* xpc = nsContentUtils::XPConnect(); + NS_ENSURE_TRUE(xpc, NS_ERROR_UNEXPECTED); + + nsAXPCNativeCallContext* cc; + nsresult rv = xpc->GetCurrentNativeCallContext(&cc); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(cc, NS_ERROR_UNEXPECTED); + + if (mData) { + JSContext* cx; + rv = cc->GetJSContext(&cx); + NS_ENSURE_SUCCESS(rv, rv); + + JSAutoRequest ar(cx); + JSAutoStructuredCloneBuffer buffer; + buffer.adopt(cx, mData, mDataLen); + mData = nsnull; + mDataLen = 0; + + JSStructuredCloneCallbacks callbacks = { + nsDOMWorker::ReadStructuredClone, nsnull, nsnull + }; + + JSBool ok = buffer.read(mDataVal.ToJSValPtr(), cx, &callbacks); + + // Release wrapped natives now, regardless of whether or not the deserialize + // succeeded. + mWrappedNatives.Clear(); + + if (!ok) { + NS_WARNING("Failed to deserialize!"); + return NS_ERROR_FAILURE; + } + } + + jsval* retval; + rv = cc->GetRetValPtr(&retval); + NS_ENSURE_SUCCESS(rv, rv); + + cc->SetReturnValueWasSet(PR_TRUE); + *retval = mDataVal; + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerMessageEvent::GetOrigin(nsAString& aOrigin) +{ + aOrigin.Assign(mOrigin); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerMessageEvent::GetSource(nsISupports** aSource) +{ + NS_ENSURE_ARG_POINTER(aSource); + NS_IF_ADDREF(*aSource = mSource); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerMessageEvent::InitMessageEvent(const nsAString& aTypeArg, + PRBool aCanBubbleArg, + PRBool aCancelableArg, + const nsAString& aDataArg, + const nsAString& aOriginArg, + nsISupports* aSourceArg) +{ + mOrigin.Assign(aOriginArg); + mSource = aSourceArg; + return nsDOMWorkerEvent::InitEvent(aTypeArg, aCanBubbleArg, aCancelableArg); +} + +NS_IMPL_ISUPPORTS_INHERITED1(nsDOMWorkerProgressEvent, nsDOMWorkerEvent, + nsIDOMProgressEvent) + +NS_IMPL_CI_INTERFACE_GETTER2(nsDOMWorkerProgressEvent, nsIDOMEvent, + nsIDOMProgressEvent) + +NS_IMPL_THREADSAFE_DOM_CI_GETINTERFACES(nsDOMWorkerProgressEvent) + +NS_IMETHODIMP +nsDOMWorkerProgressEvent::GetLengthComputable(PRBool* aLengthComputable) +{ + NS_ENSURE_ARG_POINTER(aLengthComputable); + *aLengthComputable = mLengthComputable; + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerProgressEvent::GetLoaded(PRUint64* aLoaded) +{ + NS_ENSURE_ARG_POINTER(aLoaded); + *aLoaded = mLoaded; + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerProgressEvent::GetTotal(PRUint64* aTotal) +{ + NS_ENSURE_ARG_POINTER(aTotal); + *aTotal = mTotal; + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerProgressEvent::InitProgressEvent(const nsAString_internal& aTypeArg, + PRBool aCanBubbleArg, + PRBool aCancelableArg, + PRBool aLengthComputableArg, + PRUint64 aLoadedArg, + PRUint64 aTotalArg) +{ + mLengthComputable = aLengthComputableArg; + mLoaded = aLoadedArg; + mTotal = aTotalArg; + return nsDOMWorkerEvent::InitEvent(aTypeArg, aCanBubbleArg, aCancelableArg); +} + +NS_IMPL_THREADSAFE_ADDREF(nsDOMWorkerXHRState) +NS_IMPL_THREADSAFE_RELEASE(nsDOMWorkerXHRState) + +nsDOMWorkerXHREvent::nsDOMWorkerXHREvent(nsDOMWorkerXHRProxy* aXHRProxy) +: mXHRProxy(aXHRProxy), + mXHREventType(PR_UINT32_MAX), + mChannelID(-1), + mUploadEvent(PR_FALSE), + mProgressEvent(PR_FALSE) +{ + NS_ASSERTION(aXHRProxy, "Can't be null!"); +} + +NS_IMPL_ADDREF_INHERITED(nsDOMWorkerXHREvent, nsDOMWorkerProgressEvent) +NS_IMPL_RELEASE_INHERITED(nsDOMWorkerXHREvent, nsDOMWorkerProgressEvent) + +NS_INTERFACE_MAP_BEGIN(nsDOMWorkerXHREvent) + NS_INTERFACE_MAP_ENTRY(nsIRunnable) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIDOMProgressEvent, mProgressEvent) +NS_INTERFACE_MAP_END_INHERITING(nsDOMWorkerEvent) + +NS_IMETHODIMP +nsDOMWorkerXHREvent::GetInterfaces(PRUint32* aCount, + nsIID*** aArray) +{ + PRUint32 count = *aCount = mProgressEvent ? 2 : 1; + + *aArray = (nsIID**)nsMemory::Alloc(sizeof(nsIID*) * count); + + if (mProgressEvent) { + (*aArray)[--count] = + (nsIID*)nsMemory::Clone(&NS_GET_IID(nsIDOMProgressEvent), sizeof(nsIID)); + } + + (*aArray)[--count] = + (nsIID *)nsMemory::Clone(&NS_GET_IID(nsIDOMEvent), sizeof(nsIID)); + + NS_ASSERTION(!count, "Bad math!"); + return NS_OK; +} + +nsresult +nsDOMWorkerXHREvent::Init(PRUint32 aXHREventType, + const nsAString& aType, + nsIDOMEvent* aEvent, + SnapshotChoice aSnapshot) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(aEvent, "Don't pass null here!"); + + mXHREventType = aXHREventType; + + // Only set a channel id if we're not going to be run immediately. + mChannelID = mXHRProxy->mSyncEventQueue ? -1 : mXHRProxy->ChannelID(); + + mTarget = static_cast(mXHRProxy->mWorkerXHR); + NS_ENSURE_TRUE(mTarget, NS_ERROR_UNEXPECTED); + + mXHRWN = mXHRProxy->mWorkerXHR->GetWrappedNative(); + NS_ENSURE_STATE(mXHRWN); + + nsCOMPtr mainThreadTarget; + nsresult rv = aEvent->GetTarget(getter_AddRefs(mainThreadTarget)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_STATE(mainThreadTarget); + + nsCOMPtr upload(do_QueryInterface(mainThreadTarget)); + if (upload) { + mUploadEvent = PR_TRUE; + mTarget = + static_cast(mXHRProxy->mWorkerXHR->mUpload); + } + else { + mUploadEvent = PR_FALSE; + mTarget = static_cast(mXHRProxy->mWorkerXHR); + } + NS_ASSERTION(mTarget, "Null target!"); + + PRBool bubbles; + rv = aEvent->GetBubbles(&bubbles); + NS_ENSURE_SUCCESS(rv, rv); + + PRBool cancelable; + rv = aEvent->GetCancelable(&cancelable); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aEvent->GetTimeStamp(&mTimeStamp); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aEvent->GetEventPhase(&mEventPhase); + NS_ENSURE_SUCCESS(rv, rv); + NS_ASSERTION(mEventPhase == nsIDOMEvent::AT_TARGET, "Unsupported phase!"); + + nsCOMPtr progressEvent(do_QueryInterface(aEvent)); + if (progressEvent) { + mProgressEvent = PR_TRUE; + + PRBool lengthComputable; + rv = progressEvent->GetLengthComputable(&lengthComputable); + NS_ENSURE_SUCCESS(rv, rv); + + PRUint64 loaded; + rv = progressEvent->GetLoaded(&loaded); + NS_ENSURE_SUCCESS(rv, rv); + + PRUint64 total; + rv = progressEvent->GetTotal(&total); + NS_ENSURE_SUCCESS(rv, rv); + + rv = InitProgressEvent(aType, bubbles, cancelable, lengthComputable, loaded, + total); + NS_ENSURE_SUCCESS(rv, rv); + } + else { + mProgressEvent = PR_FALSE; + + rv = InitEvent(aType, bubbles, cancelable); + NS_ENSURE_SUCCESS(rv, rv); + } + + mState = new nsDOMWorkerXHRState(); + NS_ENSURE_TRUE(mState, NS_ERROR_OUT_OF_MEMORY); + + if (aSnapshot == SNAPSHOT) { + SnapshotXHRState(mXHRProxy->mXHR, mState); + } + + return NS_OK; +} + +/* static */ +void +nsDOMWorkerXHREvent::SnapshotXHRState(nsIXMLHttpRequest* aXHR, + nsDOMWorkerXHRState* aState) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(aXHR && aState, "Don't pass null here!"); + + aState->responseTextResult = aXHR->GetResponseText(aState->responseText); + aState->statusTextResult = aXHR->GetStatusText(aState->statusText); + aState->statusResult = aXHR->GetStatus(&aState->status); + aState->readyStateResult = aXHR->GetReadyState(&aState->readyState); +} + +NS_IMETHODIMP +nsDOMWorkerXHREvent::Run() +{ + nsresult rv = mXHRProxy->HandleWorkerEvent(this, mUploadEvent); + + // Prevent reference cycles by releasing this here. + mXHRProxy = nsnull; + + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} + +NS_IMPL_ISUPPORTS_INHERITED1(nsDOMWorkerErrorEvent, nsDOMWorkerEvent, + nsIWorkerErrorEvent) + +NS_IMPL_CI_INTERFACE_GETTER2(nsDOMWorkerErrorEvent, nsIDOMEvent, + nsIWorkerErrorEvent) + +NS_IMPL_THREADSAFE_DOM_CI_GETINTERFACES(nsDOMWorkerErrorEvent) + +nsresult +nsDOMWorkerErrorEvent::GetMessage(nsAString& aMessage) +{ + aMessage.Assign(mMessage); + return NS_OK; +} + +nsresult +nsDOMWorkerErrorEvent::GetFilename(nsAString& aFilename) +{ + aFilename.Assign(mFilename); + return NS_OK; +} + +nsresult +nsDOMWorkerErrorEvent::GetLineno(PRUint32* aLineno) +{ + NS_ENSURE_ARG_POINTER(aLineno); + *aLineno = mLineno; + return NS_OK; +} + +nsresult +nsDOMWorkerErrorEvent::InitErrorEvent(const nsAString& aTypeArg, + PRBool aCanBubbleArg, + PRBool aCancelableArg, + const nsAString& aMessageArg, + const nsAString& aFilenameArg, + PRUint32 aLinenoArg) +{ + mMessage.Assign(aMessageArg); + mFilename.Assign(aFilenameArg); + mLineno = aLinenoArg; + return InitEvent(aTypeArg, aCanBubbleArg, aCancelableArg); +} diff --git a/dom/src/threads/nsDOMWorkerEvents.h b/dom/src/threads/nsDOMWorkerEvents.h new file mode 100644 index 000000000000..269dac84511c --- /dev/null +++ b/dom/src/threads/nsDOMWorkerEvents.h @@ -0,0 +1,340 @@ +/* -*- 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 Web Workers. + * + * 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 (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 __NSDOMWORKEREVENTS_H__ +#define __NSDOMWORKEREVENTS_H__ + +#include "nsIClassInfo.h" +#include "nsIDOMEvent.h" +#include "nsIDOMEventTarget.h" +#include "nsIDOMProgressEvent.h" +#include "nsIDOMWorkers.h" +#include "nsIRunnable.h" + +#include "jsapi.h" +#include "jsutil.h" +#include "nsAutoJSValHolder.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsStringGlue.h" + +#include "nsDOMWorkerMacros.h" + +class nsDOMWorkerXHRProxy; +class nsIXMLHttpRequest; +class nsIXPConnectWrappedNative; + +/* 4d5794d6-98ab-4a6b-ad5a-8ed1fa1d4839 */ +#define NS_IDOMWORKERPRIVATEEVENT_IID \ +{ \ + 0x4d5794d6, \ + 0x98ab, \ + 0x4a6b, \ + { 0xad, 0x5a, 0x8e, 0xd1, 0xfa, 0x1d, 0x48, 0x39 } \ +} + +class nsIDOMWorkerPrivateEvent : public nsIDOMEvent +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IDOMWORKERPRIVATEEVENT_IID) + virtual PRBool PreventDefaultCalled() = 0; +}; + +#define NS_FORWARD_NSIDOMEVENT_SPECIAL \ + NS_IMETHOD GetType(nsAString& aType) \ + { return mEvent->GetType(aType); } \ + NS_IMETHOD GetTarget(nsIDOMEventTarget** aTarget) \ + { return mEvent->GetTarget(aTarget); } \ + NS_IMETHOD GetCurrentTarget(nsIDOMEventTarget** aCurrentTarget) \ + { return mEvent->GetCurrentTarget(aCurrentTarget); } \ + NS_IMETHOD GetEventPhase(PRUint16* aEventPhase) \ + { return mEvent->GetEventPhase(aEventPhase); } \ + NS_IMETHOD GetBubbles(PRBool* aBubbles) \ + { return mEvent->GetBubbles(aBubbles); } \ + NS_IMETHOD GetCancelable(PRBool* aCancelable) \ + { return mEvent->GetCancelable(aCancelable); } \ + NS_IMETHOD GetTimeStamp(DOMTimeStamp* aTimeStamp) \ + { return mEvent->GetTimeStamp(aTimeStamp); } \ + NS_IMETHOD StopPropagation() \ + { return mEvent->StopPropagation(); } + +#define NS_FORWARD_NSIDOMPROGRESSEVENT_SPECIAL \ + NS_IMETHOD GetLengthComputable(PRBool* aLengthComputable) \ + { return mProgressEvent->GetLengthComputable(aLengthComputable); } \ + NS_IMETHOD GetLoaded(PRUint64* aLoaded) \ + { return mProgressEvent->GetLoaded(aLoaded); } \ + NS_IMETHOD GetTotal(PRUint64* aTotal) \ + { return mProgressEvent->GetTotal(aTotal); } + +#define NS_FORWARD_NSIWORKERMESSAGEEVENT_SPECIAL \ + NS_IMETHOD GetData(nsAString& aData) \ + { return mMessageEvent->GetData(aData); } \ + NS_IMETHOD GetOrigin(nsAString& aOrigin) \ + { return mMessageEvent->GetOrigin(aOrigin); } \ + NS_IMETHOD GetSource(nsISupports** aSource) \ + { return mMessageEvent->GetSource(aSource); } + +#define NS_FORWARD_NSIWORKERERROREVENT_SPECIAL \ + NS_IMETHOD GetMessage(nsAString& aMessage) \ + { return mErrorEvent->GetMessage(aMessage); } \ + NS_IMETHOD GetFilename(nsAString& aFilename) \ + { return mErrorEvent->GetFilename(aFilename); } \ + NS_IMETHOD GetLineno(PRUint32* aLineno) \ + { return mErrorEvent->GetLineno(aLineno); } + +class nsDOMWorkerPrivateEvent : public nsIDOMWorkerPrivateEvent, + public nsIDOMProgressEvent, + public nsIWorkerMessageEvent, + public nsIWorkerErrorEvent, + public nsIClassInfo +{ +public: + NS_DECL_ISUPPORTS + NS_FORWARD_NSIDOMEVENT_SPECIAL + NS_FORWARD_NSIWORKERMESSAGEEVENT_SPECIAL + NS_FORWARD_NSIDOMPROGRESSEVENT_SPECIAL + NS_FORWARD_NSIWORKERERROREVENT_SPECIAL + NS_DECL_NSICLASSINFO + + nsDOMWorkerPrivateEvent(nsIDOMEvent* aEvent); + + NS_IMETHOD PreventDefault(); + + NS_IMETHOD InitEvent(const nsAString& aEventType, + PRBool aCanBubble, + PRBool aCancelable); + + NS_IMETHOD InitProgressEvent(const nsAString& aTypeArg, + PRBool aCanBubbleArg, + PRBool aCancelableArg, + PRBool aLengthComputableArg, + PRUint64 aLoadedArg, + PRUint64 aTotalArg); + + NS_IMETHOD InitMessageEvent(const nsAString& aTypeArg, + PRBool aCanBubbleArg, + PRBool aCancelableArg, + const nsAString& aDataArg, + const nsAString& aOriginArg, + nsISupports* aSourceArg); + + NS_IMETHOD InitErrorEvent(const nsAString& aTypeArg, + PRBool aCanBubbleArg, + PRBool aCancelableArg, + const nsAString& aMessageArg, + const nsAString& aFilenameArg, + PRUint32 aLinenoArg); + + NS_IMETHOD GetDefaultPrevented(PRBool* aRetVal); + + virtual PRBool PreventDefaultCalled(); + +private: + nsCOMPtr mEvent; + nsCOMPtr mProgressEvent; + nsCOMPtr mMessageEvent; + nsCOMPtr mErrorEvent; + PRBool mPreventDefaultCalled; +}; + +class nsDOMWorkerEvent : public nsIDOMEvent, + public nsIClassInfo +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMEVENT + NS_DECL_NSICLASSINFO + + nsDOMWorkerEvent() + : mEventPhase(nsIDOMEvent::AT_TARGET), mTimeStamp(0), mBubbles(PR_FALSE), + mCancelable(PR_FALSE), mPreventDefaultCalled(PR_FALSE) { } + + void SetTarget(nsIDOMEventTarget* aTarget) { + mTarget = aTarget; + } + + PRBool PreventDefaultCalled() { + return PRBool(mPreventDefaultCalled); + } + +protected: + virtual ~nsDOMWorkerEvent() { } + + nsString mType; + nsCOMPtr mTarget; + PRUint16 mEventPhase; + DOMTimeStamp mTimeStamp; + PRPackedBool mBubbles; + PRPackedBool mCancelable; + PRPackedBool mPreventDefaultCalled; +}; + +class nsDOMWorkerMessageEvent : public nsDOMWorkerEvent, + public nsIWorkerMessageEvent +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_FORWARD_NSIDOMEVENT(nsDOMWorkerEvent::) + NS_DECL_NSIWORKERMESSAGEEVENT + NS_DECL_NSICLASSINFO_GETINTERFACES + + nsDOMWorkerMessageEvent() : mData(nsnull) { } + ~nsDOMWorkerMessageEvent(); + + nsresult SetJSData(JSContext* aCx, + JSAutoStructuredCloneBuffer& aBuffer, + nsTArray >& aWrappedNatives); + +protected: + nsString mOrigin; + nsCOMPtr mSource; + + nsAutoJSValHolder mDataVal; + uint64* mData; + size_t mDataLen; + nsTArray > mWrappedNatives; +}; + +class nsDOMWorkerProgressEvent : public nsDOMWorkerEvent, + public nsIDOMProgressEvent +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_FORWARD_NSIDOMEVENT(nsDOMWorkerEvent::) + NS_DECL_NSIDOMPROGRESSEVENT + NS_DECL_NSICLASSINFO_GETINTERFACES + + nsDOMWorkerProgressEvent() + : mLoaded(0), mTotal(0), mLengthComputable(PR_FALSE) { } + +protected: + PRUint64 mLoaded; + PRUint64 mTotal; + PRBool mLengthComputable; +}; + +class nsDOMWorkerXHRState +{ +public: + nsDOMWorkerXHRState() + : responseTextResult(NS_OK), statusTextResult(NS_OK), status(NS_OK), + statusResult(NS_OK), readyState(0), readyStateResult(NS_OK) { } + + NS_IMETHOD_(nsrefcnt) AddRef(); + NS_IMETHOD_(nsrefcnt) Release(); + + nsString responseText; + nsresult responseTextResult; + + nsCString statusText; + nsresult statusTextResult; + + nsresult status; + nsresult statusResult; + + PRUint16 readyState; + nsresult readyStateResult; + +protected: + virtual ~nsDOMWorkerXHRState() { } + + nsAutoRefCnt mRefCnt; +}; + +class nsDOMWorkerXHREvent : public nsDOMWorkerProgressEvent, + public nsIRunnable +{ + friend class nsDOMWorkerXHRProxy; + +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIRUNNABLE + NS_DECL_NSICLASSINFO_GETINTERFACES + + enum SnapshotChoice { + SNAPSHOT, + NO_SNAPSHOT + }; + + nsDOMWorkerXHREvent(nsDOMWorkerXHRProxy* aXHRProxy); + + nsresult Init(PRUint32 aXHREventType, + const nsAString& aType, + nsIDOMEvent* aEvent, + SnapshotChoice = SNAPSHOT); + + static void SnapshotXHRState(nsIXMLHttpRequest* aXHR, + nsDOMWorkerXHRState* aState); + + already_AddRefed ForgetState() { + return mState.forget(); + } + +protected: + nsDOMWorkerXHRState* GetState() { + return mState; + } + + nsRefPtr mXHRProxy; + nsCOMPtr mXHRWN; + nsRefPtr mState; + PRUint32 mXHREventType; + PRInt32 mChannelID; + PRPackedBool mUploadEvent; + PRPackedBool mProgressEvent; +}; + +class nsDOMWorkerErrorEvent : public nsDOMWorkerEvent, + public nsIWorkerErrorEvent +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_FORWARD_NSIDOMEVENT(nsDOMWorkerEvent::) + NS_DECL_NSIWORKERERROREVENT + NS_DECL_NSICLASSINFO_GETINTERFACES + + nsDOMWorkerErrorEvent() + : mLineno(0) { } + +protected: + nsString mMessage; + nsString mFilename; + PRUint32 mLineno; +}; + +#endif /* __NSDOMWORKEREVENTS_H__ */ diff --git a/dom/src/threads/nsDOMWorkerLocation.cpp b/dom/src/threads/nsDOMWorkerLocation.cpp new file mode 100644 index 000000000000..776ab9176c39 --- /dev/null +++ b/dom/src/threads/nsDOMWorkerLocation.cpp @@ -0,0 +1,223 @@ +/* -*- 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) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Ben Turner (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 "nsDOMWorkerLocation.h" + +#include "nsIClassInfoImpl.h" +#include "nsITextToSubURI.h" +#include "nsIURL.h" + +#include "nsDOMWorkerMacros.h" + +#include "nsAutoPtr.h" +#include "nsEscape.h" +#include "nsNetUtil.h" + +#define XPC_MAP_CLASSNAME nsDOMWorkerLocation +#define XPC_MAP_QUOTED_CLASSNAME "WorkerLocation" + +#define XPC_MAP_FLAGS \ + nsIXPCScriptable::USE_JSSTUB_FOR_ADDPROPERTY | \ + nsIXPCScriptable::USE_JSSTUB_FOR_DELPROPERTY | \ + nsIXPCScriptable::USE_JSSTUB_FOR_SETPROPERTY | \ + nsIXPCScriptable::DONT_ENUM_QUERY_INTERFACE | \ + nsIXPCScriptable::CLASSINFO_INTERFACES_ONLY | \ + nsIXPCScriptable::DONT_REFLECT_INTERFACE_NAMES + +#include "xpc_map_end.h" + +NS_IMPL_THREADSAFE_ISUPPORTS3(nsDOMWorkerLocation, nsIWorkerLocation, + nsIClassInfo, + nsIXPCScriptable) + +NS_IMPL_CI_INTERFACE_GETTER1(nsDOMWorkerLocation, nsIWorkerLocation) + +NS_IMPL_THREADSAFE_DOM_CI_GETINTERFACES(nsDOMWorkerLocation) +NS_IMPL_THREADSAFE_DOM_CI_ALL_THE_REST(nsDOMWorkerLocation) + +NS_IMETHODIMP +nsDOMWorkerLocation::GetHelperForLanguage(PRUint32 aLanguage, + nsISupports** _retval) +{ + if (aLanguage == nsIProgrammingLanguage::JAVASCRIPT) { + NS_ADDREF(*_retval = NS_ISUPPORTS_CAST(nsIWorkerLocation*, this)); + } + else { + *_retval = nsnull; + } + return NS_OK; +} + +// static +already_AddRefed +nsDOMWorkerLocation::NewLocation(nsIURL* aURL) +{ + NS_ASSERTION(aURL, "Don't hand me a null pointer!"); + + nsAutoPtr location(new nsDOMWorkerLocation()); + NS_ENSURE_TRUE(location, nsnull); + + nsresult rv = aURL->GetSpec(location->mHref); + NS_ENSURE_SUCCESS(rv, nsnull); + + rv = aURL->GetHost(location->mHostname); + NS_ENSURE_SUCCESS(rv, nsnull); + + rv = aURL->GetPath(location->mPathname); + NS_ENSURE_SUCCESS(rv, nsnull); + + nsCString temp; + + rv = aURL->GetQuery(temp); + NS_ENSURE_SUCCESS(rv, nsnull); + if (!temp.IsEmpty()) { + location->mSearch.AssignLiteral("?"); + location->mSearch.Append(temp); + } + + rv = aURL->GetRef(temp); + NS_ENSURE_SUCCESS(rv, nsnull); + + if (!temp.IsEmpty()) { + nsAutoString unicodeRef; + + nsCOMPtr converter = + do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + nsCString charset; + rv = aURL->GetOriginCharset(charset); + if (NS_SUCCEEDED(rv)) { + rv = converter->UnEscapeURIForUI(charset, temp, unicodeRef); + if (NS_SUCCEEDED(rv)) { + location->mHash.AssignLiteral("#"); + location->mHash.Append(NS_ConvertUTF16toUTF8(unicodeRef)); + } + } + } + + if (NS_FAILED(rv)) { + location->mHash.AssignLiteral("#"); + location->mHash.Append(temp); + } + } + + rv = aURL->GetScheme(location->mProtocol); + NS_ENSURE_SUCCESS(rv, nsnull); + + location->mProtocol.AppendLiteral(":"); + + PRInt32 port; + rv = aURL->GetPort(&port); + if (NS_SUCCEEDED(rv) && port != -1) { + location->mPort.AppendInt(port); + + nsCAutoString host(location->mHostname); + host.AppendLiteral(":"); + host.Append(location->mPort); + + location->mHost.Assign(host); + } + else { + location->mHost.Assign(location->mHostname); + } + + NS_ADDREF(location); + return location.forget(); +} + +NS_IMETHODIMP +nsDOMWorkerLocation::GetHref(nsACString& aHref) +{ + aHref.Assign(mHref); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerLocation::GetProtocol(nsACString& aProtocol) +{ + aProtocol.Assign(mProtocol); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerLocation::GetHost(nsACString& aHost) +{ + aHost.Assign(mHost); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerLocation::GetHostname(nsACString& aHostname) +{ + aHostname.Assign(mHostname); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerLocation::GetPort(nsACString& aPort) +{ + aPort.Assign(mPort); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerLocation::GetPathname(nsACString& aPathname) +{ + aPathname.Assign(mPathname); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerLocation::GetSearch(nsACString& aSearch) +{ + aSearch.Assign(mSearch); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerLocation::GetHash(nsACString& aHash) +{ + aHash.Assign(mHash); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerLocation::ToString(nsACString& _retval) +{ + return GetHref(_retval); +} diff --git a/dom/src/threads/nsDOMWorkerLocation.h b/dom/src/threads/nsDOMWorkerLocation.h new file mode 100644 index 000000000000..e3832d20811b --- /dev/null +++ b/dom/src/threads/nsDOMWorkerLocation.h @@ -0,0 +1,77 @@ +/* -*- 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) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Ben Turner (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 __NSDOMWORKERLOCATION_H__ +#define __NSDOMWORKERLOCATION_H__ + +#include "nsIClassInfo.h" +#include "nsIDOMWorkers.h" +#include "nsIXPCScriptable.h" + +#include "nsCOMPtr.h" +#include "nsStringGlue.h" + +class nsIURL; + +class nsDOMWorkerLocation : public nsIWorkerLocation, + public nsIClassInfo, + public nsIXPCScriptable +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIWORKERLOCATION + NS_DECL_NSICLASSINFO + NS_DECL_NSIXPCSCRIPTABLE + + static already_AddRefed NewLocation(nsIURL* aURL); + +protected: + nsDOMWorkerLocation() { } + +private: + nsCString mHref; + nsCString mProtocol; + nsCString mHost; + nsCString mHostname; + nsCString mPort; + nsCString mPathname; + nsCString mSearch; + nsCString mHash; +}; + +#endif /* __NSDOMWORKERLOCATION_H__ */ diff --git a/dom/src/threads/nsDOMWorkerMacros.h b/dom/src/threads/nsDOMWorkerMacros.h new file mode 100644 index 000000000000..5dceae8fb5c9 --- /dev/null +++ b/dom/src/threads/nsDOMWorkerMacros.h @@ -0,0 +1,135 @@ +/* -*- 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 Web Workers. + * + * 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 (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 __NSDOMWORKERMACROS_H__ +#define __NSDOMWORKERMACROS_H__ + +// Macro to generate nsIClassInfo methods for these threadsafe DOM classes +#define NS_IMPL_THREADSAFE_DOM_CI_GETINTERFACES(_class) \ +NS_IMETHODIMP \ +_class::GetInterfaces(PRUint32* _count, nsIID*** _array) \ +{ \ + return NS_CI_INTERFACE_GETTER_NAME(_class)(_count, _array); \ +} \ + +#define NS_IMPL_THREADSAFE_DOM_CI_HELPER(_class) \ +NS_IMETHODIMP \ +_class::GetHelperForLanguage(PRUint32 _language, nsISupports** _retval) \ +{ \ + *_retval = nsnull; \ + return NS_OK; \ +} + +#define NS_IMPL_THREADSAFE_DOM_CI_ALL_THE_REST(_class) \ +NS_IMETHODIMP \ +_class::GetContractID(char** _contractID) \ +{ \ + *_contractID = nsnull; \ + return NS_OK; \ +} \ + \ +NS_IMETHODIMP \ +_class::GetClassDescription(char** _classDescription) \ +{ \ + *_classDescription = nsnull; \ + return NS_OK; \ +} \ + \ +NS_IMETHODIMP \ +_class::GetClassID(nsCID** _classID) \ +{ \ + *_classID = nsnull; \ + return NS_OK; \ +} \ + \ +NS_IMETHODIMP \ +_class::GetImplementationLanguage(PRUint32* _language) \ +{ \ + *_language = nsIProgrammingLanguage::CPLUSPLUS; \ + return NS_OK; \ +} \ + \ +NS_IMETHODIMP \ +_class::GetFlags(PRUint32* _flags) \ +{ \ + *_flags = nsIClassInfo::THREADSAFE | nsIClassInfo::DOM_OBJECT; \ + return NS_OK; \ +} \ + \ +NS_IMETHODIMP \ +_class::GetClassIDNoAlloc(nsCID* _classIDNoAlloc) \ +{ \ + return NS_ERROR_NOT_AVAILABLE; \ +} + +#define NS_IMPL_THREADSAFE_DOM_CI(_class) \ +NS_IMPL_THREADSAFE_DOM_CI_GETINTERFACES(_class) \ +NS_IMPL_THREADSAFE_DOM_CI_HELPER(_class) \ +NS_IMPL_THREADSAFE_DOM_CI_ALL_THE_REST(_class) + +#define NS_FORWARD_NSICLASSINFO_NOGETINTERFACES(_to) \ + NS_IMETHOD GetHelperForLanguage(PRUint32 aLanguage, nsISupports** _retval) \ + { return _to GetHelperForLanguage(aLanguage, _retval); } \ + NS_IMETHOD GetContractID(char** aContractID) \ + { return _to GetContractID(aContractID); } \ + NS_IMETHOD GetClassDescription(char** aClassDescription) \ + { return _to GetClassDescription(aClassDescription); } \ + NS_IMETHOD GetClassID(nsCID** aClassID) \ + { return _to GetClassID(aClassID); } \ + NS_IMETHOD GetImplementationLanguage(PRUint32* aImplementationLanguage) \ + { return _to GetImplementationLanguage(aImplementationLanguage); } \ + NS_IMETHOD GetFlags(PRUint32* aFlags) \ + { return _to GetFlags(aFlags); } \ + NS_IMETHOD GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) \ + { return _to GetClassIDNoAlloc(aClassIDNoAlloc); } + +#define NS_DECL_NSICLASSINFO_GETINTERFACES \ + NS_IMETHOD GetInterfaces(PRUint32* aCount, nsIID*** aArray); + +// Don't know why nsISupports.idl defines this out... +#define NS_FORWARD_NSISUPPORTS(_to) \ + NS_IMETHOD QueryInterface(const nsIID& uuid, void** result) { \ + return _to QueryInterface(uuid, result); \ + } \ + NS_IMETHOD_(nsrefcnt) AddRef(void) { return _to AddRef(); } \ + NS_IMETHOD_(nsrefcnt) Release(void) { return _to Release(); } + +#define JSON_PRIMITIVE_PROPNAME \ + "primitive" + +#endif /* __NSDOMWORKERMACROS_H__ */ diff --git a/dom/src/threads/nsDOMWorkerMessageHandler.cpp b/dom/src/threads/nsDOMWorkerMessageHandler.cpp new file mode 100644 index 000000000000..693ea2af4938 --- /dev/null +++ b/dom/src/threads/nsDOMWorkerMessageHandler.cpp @@ -0,0 +1,440 @@ +/* -*- 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 Web Workers. + * + * 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 (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 "nsDOMWorkerMessageHandler.h" + +#include "nsIDOMEvent.h" +#include "nsIXPConnect.h" + +#include "nsContentUtils.h" + +#include "nsDOMThreadService.h" +#include "nsDOMWorkerEvents.h" + +NS_IMPL_THREADSAFE_ADDREF(nsDOMWorkerEventListenerBase) +NS_IMPL_THREADSAFE_RELEASE(nsDOMWorkerEventListenerBase) + +nsresult +nsDOMWorkerWeakEventListener::Init(nsIDOMEventListener* aListener) +{ + NS_ENSURE_ARG_POINTER(aListener); + + nsCOMPtr wrappedJS(do_QueryInterface(aListener)); + NS_ENSURE_TRUE(wrappedJS, NS_NOINTERFACE); + + JSObject* obj; + nsresult rv = wrappedJS->GetJSObject(&obj); + NS_ENSURE_SUCCESS(rv, rv); + + mObj = obj; + + return NS_OK; +} + +already_AddRefed +nsDOMWorkerWeakEventListener::GetListener() +{ + JSContext* cx = nsDOMThreadService::GetCurrentContext(); + NS_ENSURE_TRUE(cx, nsnull); + + nsIXPConnect* xpc = nsContentUtils::XPConnect(); + + nsCOMPtr listener; + nsresult rv = xpc->WrapJS(cx, mObj, NS_GET_IID(nsIDOMEventListener), + getter_AddRefs(listener)); + NS_ENSURE_SUCCESS(rv, nsnull); + + return listener.forget(); +} + +nsDOMWorkerWrappedWeakEventListener:: +nsDOMWorkerWrappedWeakEventListener(nsDOMWorkerWeakEventListener* aInner) +: mInner(aInner) +{ + NS_ASSERTION(aInner, "Null pointer!"); +} + +NS_IMPL_THREADSAFE_ISUPPORTS2(nsDOMWorkerMessageHandler, + nsIDOMEventTarget, + nsIClassInfo) + +NS_IMPL_CI_INTERFACE_GETTER1(nsDOMWorkerMessageHandler, + nsIDOMEventTarget) + +NS_IMPL_THREADSAFE_DOM_CI(nsDOMWorkerMessageHandler) + +const nsDOMWorkerMessageHandler::ListenerCollection* +nsDOMWorkerMessageHandler::GetListenerCollection(const nsAString& aType) const +{ + PRUint32 count = mCollections.Length(); + for (PRUint32 index = 0; index < count; index++) { + const ListenerCollection& collection = mCollections[index]; + if (collection.type.Equals(aType)) { + return &collection; + } + } + return nsnull; +} + +void +nsDOMWorkerMessageHandler::GetListenersForType(const nsAString& aType, + ListenerArray& _retval) const +{ + _retval.Clear(); + + const ListenerCollection* collection = GetListenerCollection(aType); + if (collection) { + PRUint32 count = collection->listeners.Length(); + + if (!_retval.SetLength(count)) { + NS_WARNING("Out of memory!"); + return; + } + + for (PRUint32 index = 0; index < count; index++) { + nsCOMPtr listener = + collection->listeners[index]->GetListener(); + _retval[index].swap(listener); + } + } +} + +nsresult +nsDOMWorkerMessageHandler::SetOnXListener(const nsAString& aType, + nsIDOMEventListener* aListener) +{ + nsRefPtr wrappedListener; + + ListenerCollection* collection = + const_cast(GetListenerCollection(aType)); + +#ifdef DEBUG + PRBool removed; +#endif + + if (collection) { + wrappedListener.swap(collection->onXListener); + if (wrappedListener) { +#ifdef DEBUG + removed = +#endif + collection->listeners.RemoveElement(wrappedListener); + NS_ASSERTION(removed, "Element wasn't in the list!"); + } + } + + if (!aListener) { + if (collection && !collection->listeners.Length()) { +#ifdef DEBUG + removed = +#endif + mCollections.RemoveElement(*collection); + NS_ASSERTION(removed, "Element wasn't in the list!"); + } + return NS_OK; + } + + nsRefPtr weakListener = + new nsDOMWorkerWeakEventListener(); + NS_ENSURE_TRUE(weakListener, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv = weakListener->Init(aListener); + NS_ENSURE_SUCCESS(rv, rv); + + wrappedListener = new nsDOMWorkerWrappedWeakEventListener(weakListener); + NS_ENSURE_TRUE(wrappedListener, NS_ERROR_OUT_OF_MEMORY); + + if (!collection) { + collection = mCollections.AppendElement(aType); + NS_ENSURE_TRUE(collection, NS_ERROR_OUT_OF_MEMORY); + } + + WeakListener* newListener = + collection->listeners.AppendElement(wrappedListener); + NS_ENSURE_TRUE(newListener, NS_ERROR_OUT_OF_MEMORY); + + wrappedListener.swap(collection->onXListener); + return NS_OK; +} + +already_AddRefed +nsDOMWorkerMessageHandler::GetOnXListener(const nsAString& aType) const +{ + const ListenerCollection* collection = GetListenerCollection(aType); + if (collection && collection->onXListener) { + return collection->onXListener->GetListener(); + } + + return nsnull; +} + +void +nsDOMWorkerMessageHandler::ClearListeners(const nsAString& aType) +{ + PRUint32 count = mCollections.Length(); + for (PRUint32 index = 0; index < count; index++) { + if (mCollections[index].type.Equals(aType)) { + mCollections.RemoveElementAt(index); + return; + } + } +} + +PRBool +nsDOMWorkerMessageHandler::HasListeners(const nsAString& aType) +{ + const ListenerCollection* collection = GetListenerCollection(aType); + return collection && collection->listeners.Length(); +} + +void +nsDOMWorkerMessageHandler::ClearAllListeners() +{ + mCollections.Clear(); +} + +void +nsDOMWorkerMessageHandler::Trace(JSTracer* aTracer) +{ + PRUint32 cCount = mCollections.Length(); + for (PRUint32 cIndex = 0; cIndex < cCount; cIndex++) { + const ListenerCollection& collection = mCollections[cIndex]; + PRUint32 lCount = collection.listeners.Length(); + for (PRUint32 lIndex = 0; lIndex < lCount; lIndex++) { + JSObject* obj = collection.listeners[lIndex]->GetJSObject(); + NS_ASSERTION(obj, "Null object!"); + JS_SET_TRACING_DETAILS(aTracer, nsnull, this, 0); + JS_CallTracer(aTracer, obj, JSTRACE_OBJECT); + } + } +} + +/** + * See nsIDOMEventTarget + */ +NS_IMETHODIMP +nsDOMWorkerMessageHandler::RemoveEventListener(const nsAString& aType, + nsIDOMEventListener* aListener, + PRBool aUseCapture) +{ + ListenerCollection* collection = + const_cast(GetListenerCollection(aType)); + + if (collection) { + PRUint32 count = collection->listeners.Length(); + for (PRUint32 index = 0; index < count; index++) { + WeakListener& weakListener = collection->listeners[index]; + if (weakListener == collection->onXListener) { + continue; + } + nsCOMPtr listener = weakListener->GetListener(); + if (listener == aListener) { + collection->listeners.RemoveElementAt(index); + break; + } + } + + if (!collection->listeners.Length()) { +#ifdef DEBUG + PRBool removed = +#endif + mCollections.RemoveElement(*collection); + NS_ASSERTION(removed, "Somehow this wasn't in the list!"); + } + } + + return NS_OK; +} + +/** + * See nsIDOMEventTarget + */ +NS_IMETHODIMP +nsDOMWorkerMessageHandler::DispatchEvent(nsIDOMEvent* aEvent, + PRBool* _retval) +{ + NS_ENSURE_ARG_POINTER(aEvent); + + nsCOMPtr event; + + if (_retval) { + event = do_QueryInterface(aEvent); + if (!event) { + event = new nsDOMWorkerPrivateEvent(aEvent); + NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY); + } + aEvent = event; + } + + nsAutoString type; + nsresult rv = aEvent->GetType(type); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoTArray listeners; + GetListenersForType(type, listeners); + + PRUint32 count = listeners.Length(); + for (PRUint32 index = 0; index < count; index++) { + const Listener& listener = listeners[index]; + NS_ASSERTION(listener, "Null listener in array!"); + + listener->HandleEvent(aEvent); + } + + if (_retval) { + *_retval = event->PreventDefaultCalled(); + } + + return NS_OK; +} + +/** + * See nsIDOMEventTarget + */ +NS_IMETHODIMP +nsDOMWorkerMessageHandler::AddEventListener(const nsAString& aType, + nsIDOMEventListener* aListener, + PRBool aUseCapture, + PRBool aWantsUntrusted, + PRUint8 aOptionalArgc) +{ + // We don't support aWantsUntrusted yet. + NS_ENSURE_TRUE(aOptionalArgc < 2, NS_ERROR_NOT_IMPLEMENTED); + + ListenerCollection* collection = + const_cast(GetListenerCollection(aType)); + + if (!collection) { + collection = mCollections.AppendElement(aType); + NS_ENSURE_TRUE(collection, NS_ERROR_OUT_OF_MEMORY); + } + + nsRefPtr weakListener = + new nsDOMWorkerWeakEventListener(); + NS_ENSURE_TRUE(weakListener, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv = weakListener->Init(aListener); + NS_ENSURE_SUCCESS(rv, rv); + + WeakListener* newListener = collection->listeners.AppendElement(weakListener); + NS_ENSURE_TRUE(newListener, NS_ERROR_OUT_OF_MEMORY); + + return NS_OK; +} + +nsIDOMEventTarget * +nsDOMWorkerMessageHandler::GetTargetForDOMEvent() +{ + NS_ERROR("Should not be called"); + return nsnull; +} + +nsIDOMEventTarget * +nsDOMWorkerMessageHandler::GetTargetForEventTargetChain() +{ + NS_ERROR("Should not be called"); + return nsnull; +} + +nsresult +nsDOMWorkerMessageHandler::PreHandleEvent(nsEventChainPreVisitor & aVisitor) +{ + NS_ERROR("Should not be called"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsDOMWorkerMessageHandler::WillHandleEvent(nsEventChainPostVisitor & aVisitor) +{ + NS_ERROR("Should not be called"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsDOMWorkerMessageHandler::PostHandleEvent(nsEventChainPostVisitor & aVisitor) +{ + NS_ERROR("Should not be called"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsDOMWorkerMessageHandler::DispatchDOMEvent(nsEvent *aEvent, nsIDOMEvent *aDOMEvent, + nsPresContext *aPresContext, + nsEventStatus *aEventStatus) +{ + NS_ERROR("Should not be called"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsEventListenerManager* +nsDOMWorkerMessageHandler::GetListenerManager(PRBool aMayCreate) +{ + NS_ERROR("Should not be called"); + return nsnull; +} + +nsresult +nsDOMWorkerMessageHandler::AddEventListenerByIID(nsIDOMEventListener *aListener, + const nsIID & aIID) +{ + NS_ERROR("Should not be called"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsDOMWorkerMessageHandler::RemoveEventListenerByIID(nsIDOMEventListener *aListener, + const nsIID & aIID) +{ + NS_ERROR("Should not be called"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsIScriptContext* +nsDOMWorkerMessageHandler::GetContextForEventHandlers(nsresult *aRv) +{ + NS_ERROR("Should not be called"); + *aRv = NS_ERROR_NOT_IMPLEMENTED; + return nsnull; +} + +JSContext* +nsDOMWorkerMessageHandler::GetJSContextForEventHandlers() +{ + NS_ERROR("Should not be called"); + return nsnull; +} diff --git a/dom/src/threads/nsDOMWorkerMessageHandler.h b/dom/src/threads/nsDOMWorkerMessageHandler.h new file mode 100644 index 000000000000..af7f0061ccb5 --- /dev/null +++ b/dom/src/threads/nsDOMWorkerMessageHandler.h @@ -0,0 +1,175 @@ +/* -*- 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 Web Workers. + * + * 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 (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 __NSDOMWORKERMESSAGEHANDLER_H__ +#define __NSDOMWORKERMESSAGEHANDLER_H__ + +#include "nsIClassInfo.h" +#include "nsIDOMEventListener.h" +#include "nsIDOMEventTarget.h" +#include "nsIDOMWorkers.h" + +#include "nsIProgrammingLanguage.h" + +#include "jsapi.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsIClassInfoImpl.h" +#include "nsStringGlue.h" +#include "nsTArray.h" +#include "nsIWeakReference.h" + +class nsDOMWorkerEventListenerBase +{ +public: + NS_IMETHOD_(nsrefcnt) AddRef(); + NS_IMETHOD_(nsrefcnt) Release(); + + virtual already_AddRefed GetListener() = 0; + virtual JSObject* GetJSObject() = 0; + +protected: + virtual ~nsDOMWorkerEventListenerBase() { } + + nsAutoRefCnt mRefCnt; +}; + +class nsDOMWorkerWeakEventListener : public nsDOMWorkerEventListenerBase +{ +public: + nsDOMWorkerWeakEventListener() + : mObj(NULL) { } + + nsresult Init(nsIDOMEventListener* aListener); + + already_AddRefed GetListener(); + + virtual JSObject* GetJSObject() { + return mObj; + } + +private: + JSObject* mObj; +}; + +class nsDOMWorkerWrappedWeakEventListener : public nsDOMWorkerEventListenerBase +{ +public: + nsDOMWorkerWrappedWeakEventListener(nsDOMWorkerWeakEventListener* aInner); + + already_AddRefed GetListener() { + return mInner->GetListener(); + } + + virtual JSObject* GetJSObject() { + return mInner->GetJSObject(); + } + +private: + nsRefPtr mInner; +}; + +class nsDOMWorkerMessageHandler : public nsIDOMEventTarget, + public nsIClassInfo +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMEVENTTARGET + NS_DECL_NSICLASSINFO + + virtual nsresult SetOnXListener(const nsAString& aType, + nsIDOMEventListener* aListener); + + already_AddRefed + GetOnXListener(const nsAString& aType) const; + + void ClearListeners(const nsAString& aType); + + PRBool HasListeners(const nsAString& aType); + + void ClearAllListeners(); + + void Trace(JSTracer* aTracer); + +protected: + virtual ~nsDOMWorkerMessageHandler() { } + +private: + + typedef nsCOMPtr Listener; + typedef nsTArray ListenerArray; + + typedef nsRefPtr WeakListener; + typedef nsTArray WeakListenerArray; + + struct ListenerCollection { + PRBool operator==(const ListenerCollection& aOther) const { + return this == &aOther; + } + + ListenerCollection(const nsAString& aType) + : type(aType) { } + + nsString type; + WeakListenerArray listeners; + nsRefPtr onXListener; + }; + + const ListenerCollection* GetListenerCollection(const nsAString& aType) const; + + void GetListenersForType(const nsAString& aType, + ListenerArray& _retval) const; + + nsTArray mCollections; +}; + +#define NS_FORWARD_INTERNAL_NSIDOMEVENTTARGET(_to) \ + virtual nsIDOMEventTarget * GetTargetForDOMEvent(void) { return _to GetTargetForDOMEvent(); } \ + virtual nsIDOMEventTarget * GetTargetForEventTargetChain(void) { return _to GetTargetForEventTargetChain(); } \ + virtual nsresult PreHandleEvent(nsEventChainPreVisitor & aVisitor) { return _to PreHandleEvent(aVisitor); } \ + virtual nsresult WillHandleEvent(nsEventChainPostVisitor & aVisitor) { return _to WillHandleEvent(aVisitor); } \ + virtual nsresult PostHandleEvent(nsEventChainPostVisitor & aVisitor) { return _to PostHandleEvent(aVisitor); } \ + virtual nsresult DispatchDOMEvent(nsEvent *aEvent, nsIDOMEvent *aDOMEvent, nsPresContext *aPresContext, nsEventStatus *aEventStatus) { return _to DispatchDOMEvent(aEvent, aDOMEvent, aPresContext, aEventStatus); } \ + virtual nsEventListenerManager * GetListenerManager(PRBool aMayCreate) { return _to GetListenerManager(aMayCreate); } \ + virtual nsresult AddEventListenerByIID(nsIDOMEventListener *aListener, const nsIID & aIID) { return _to AddEventListenerByIID(aListener, aIID); } \ + virtual nsresult RemoveEventListenerByIID(nsIDOMEventListener *aListener, const nsIID & aIID) { return _to RemoveEventListenerByIID(aListener, aIID); } \ + virtual nsIScriptContext * GetContextForEventHandlers(nsresult *aRv NS_OUTPARAM) { return _to GetContextForEventHandlers(aRv); } \ + virtual JSContext * GetJSContextForEventHandlers(void) { return _to GetJSContextForEventHandlers(); } + + +#endif /* __NSDOMWORKERMESSAGEHANDLER_H__ */ diff --git a/dom/src/threads/nsDOMWorkerNavigator.cpp b/dom/src/threads/nsDOMWorkerNavigator.cpp new file mode 100644 index 000000000000..6f4b2714fcb4 --- /dev/null +++ b/dom/src/threads/nsDOMWorkerNavigator.cpp @@ -0,0 +1,108 @@ +/* -*- 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 (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 "nsDOMWorkerNavigator.h" + +#include "nsIClassInfoImpl.h" +#include "nsStringGlue.h" + +#include "nsDOMThreadService.h" +#include "nsDOMWorkerMacros.h" + +#define XPC_MAP_CLASSNAME nsDOMWorkerNavigator +#define XPC_MAP_QUOTED_CLASSNAME "Navigator" + +#define XPC_MAP_FLAGS \ + nsIXPCScriptable::USE_JSSTUB_FOR_ADDPROPERTY | \ + nsIXPCScriptable::USE_JSSTUB_FOR_DELPROPERTY | \ + nsIXPCScriptable::USE_JSSTUB_FOR_SETPROPERTY | \ + nsIXPCScriptable::DONT_ENUM_QUERY_INTERFACE | \ + nsIXPCScriptable::CLASSINFO_INTERFACES_ONLY | \ + nsIXPCScriptable::DONT_REFLECT_INTERFACE_NAMES + +#include "xpc_map_end.h" + +NS_IMPL_THREADSAFE_ISUPPORTS3(nsDOMWorkerNavigator, nsIWorkerNavigator, + nsIClassInfo, + nsIXPCScriptable) + +NS_IMPL_CI_INTERFACE_GETTER1(nsDOMWorkerNavigator, nsIWorkerNavigator) + +NS_IMPL_THREADSAFE_DOM_CI_GETINTERFACES(nsDOMWorkerNavigator) +NS_IMPL_THREADSAFE_DOM_CI_ALL_THE_REST(nsDOMWorkerNavigator) + +NS_IMETHODIMP +nsDOMWorkerNavigator::GetHelperForLanguage(PRUint32 aLanguage, + nsISupports** _retval) +{ + if (aLanguage == nsIProgrammingLanguage::JAVASCRIPT) { + NS_ADDREF(*_retval = NS_ISUPPORTS_CAST(nsIWorkerNavigator*, this)); + } + else { + *_retval = nsnull; + } + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerNavigator::GetAppName(nsAString& aAppName) +{ + nsDOMThreadService::get()->GetAppName(aAppName); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerNavigator::GetAppVersion(nsAString& aAppVersion) +{ + nsDOMThreadService::get()->GetAppVersion(aAppVersion); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerNavigator::GetPlatform(nsAString& aPlatform) +{ + nsDOMThreadService::get()->GetPlatform(aPlatform); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerNavigator::GetUserAgent(nsAString& aUserAgent) +{ + nsDOMThreadService::get()->GetUserAgent(aUserAgent); + return NS_OK; +} diff --git a/dom/workers/WorkerInlines.h b/dom/src/threads/nsDOMWorkerNavigator.h similarity index 73% rename from dom/workers/WorkerInlines.h rename to dom/src/threads/nsDOMWorkerNavigator.h index 9f51c654f19a..f64309243418 100644 --- a/dom/workers/WorkerInlines.h +++ b/dom/src/threads/nsDOMWorkerNavigator.h @@ -1,4 +1,4 @@ -/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* -*- 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 * @@ -12,11 +12,11 @@ * for the specific language governing rights and limitations under the * License. * - * The Original Code is Web Workers. + * The Original Code is worker threads. * * The Initial Developer of the Original Code is - * The Mozilla Foundation. - * Portions created by the Initial Developer are Copyright (C) 2011 + * Mozilla Corporation. + * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): @@ -36,22 +36,22 @@ * * ***** END LICENSE BLOCK ***** */ -BEGIN_WORKERS_NAMESPACE +#ifndef __NSDOMWORKERNAVIGATOR_H__ +#define __NSDOMWORKERNAVIGATOR_H__ -inline -JSBool -SetJSPrivateSafeish(JSContext* aCx, JSObject* aObj, PrivatizableBase* aBase) +#include "nsIClassInfo.h" +#include "nsIDOMWorkers.h" +#include "nsIXPCScriptable.h" + +class nsDOMWorkerNavigator : public nsIWorkerNavigator, + public nsIClassInfo, + public nsIXPCScriptable { - return JS_SetPrivate(aCx, aObj, aBase); -} +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIWORKERNAVIGATOR + NS_DECL_NSICLASSINFO + NS_DECL_NSIXPCSCRIPTABLE +}; -template -inline -Derived* -GetJSPrivateSafeish(JSContext* aCx, JSObject* aObj) -{ - return static_cast( - static_cast(JS_GetPrivate(aCx, aObj))); -} - -END_WORKERS_NAMESPACE +#endif /* __NSDOMWORKERNAVIGATOR_H__ */ diff --git a/dom/src/threads/nsDOMWorkerPool.cpp b/dom/src/threads/nsDOMWorkerPool.cpp new file mode 100644 index 000000000000..d9396e3d414e --- /dev/null +++ b/dom/src/threads/nsDOMWorkerPool.cpp @@ -0,0 +1,238 @@ +/* -*- 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): + * Vladimir Vukicevic (Original Author) + * Ben Turner + * + * 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 "nsDOMWorkerPool.h" + +// Interfaces +#include "nsIDocument.h" +#include "nsIDOMClassInfo.h" +#include "nsIJSContextStack.h" +#include "nsIScriptGlobalObject.h" +#include "nsIServiceManager.h" +#include "nsIThreadManager.h" +#include "nsIXPConnect.h" +#include "nsPIDOMWindow.h" + +// Other includes +#include "nsContentUtils.h" +#include "nsDOMJSUtils.h" +#include "nsProxyRelease.h" +#include "nsThreadUtils.h" + +// DOMWorker includes +#include "nsDOMThreadService.h" +#include "nsDOMWorker.h" + +using namespace mozilla; + +#define LOG(_args) PR_LOG(gDOMThreadsLog, PR_LOG_DEBUG, _args) + +nsDOMWorkerPool::nsDOMWorkerPool(nsIScriptGlobalObject* aGlobalObject, + nsIDocument* aDocument) +: mParentGlobal(aGlobalObject), + mParentDocument(aDocument), + mReentrantMonitor("nsDOMWorkerPool.mReentrantMonitor"), + mCanceled(PR_FALSE), + mSuspended(PR_FALSE), + mWindowID(aDocument ? aDocument->OuterWindowID() : 0) +{ +} + +nsDOMWorkerPool::~nsDOMWorkerPool() +{ + nsCOMPtr mainThread; + NS_GetMainThread(getter_AddRefs(mainThread)); + + nsIScriptGlobalObject* global; + mParentGlobal.forget(&global); + if (global) { + NS_ProxyRelease(mainThread, global, PR_FALSE); + } + + nsIDocument* document; + mParentDocument.forget(&document); + if (document) { + NS_ProxyRelease(mainThread, document, PR_FALSE); + } +} + +NS_IMPL_THREADSAFE_ADDREF(nsDOMWorkerPool) +NS_IMPL_THREADSAFE_RELEASE(nsDOMWorkerPool) + +nsresult +nsDOMWorkerPool::Init() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + return NS_OK; +} + +nsresult +nsDOMWorkerPool::NoteWorker(nsDOMWorker* aWorker) +{ + NS_ASSERTION(aWorker, "Null pointer!"); + + PRBool suspendWorker; + + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + nsDOMWorker** newWorker = mWorkers.AppendElement(aWorker); + NS_ENSURE_TRUE(newWorker, NS_ERROR_OUT_OF_MEMORY); + + suspendWorker = mSuspended; + } + + if (suspendWorker) { + aWorker->Suspend(); + } + + return NS_OK; +} + +void +nsDOMWorkerPool::NoteDyingWorker(nsDOMWorker* aWorker) +{ + NS_ASSERTION(aWorker, "Null pointer!"); + + PRBool removeFromThreadService = PR_FALSE; + + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + NS_ASSERTION(mWorkers.Contains(aWorker), "Worker from a different pool?!"); + mWorkers.RemoveElement(aWorker); + + if (!mCanceled && !mWorkers.Length()) { + removeFromThreadService = PR_TRUE; + } + } + + if (removeFromThreadService) { + nsRefPtr kungFuDeathGrip(this); + nsDOMThreadService::get()->NoteEmptyPool(this); + } +} + +void +nsDOMWorkerPool::GetWorkers(nsTArray& aArray) +{ + mReentrantMonitor.AssertCurrentThreadIn(); + NS_ASSERTION(!aArray.Length(), "Should be empty!"); + +#ifdef DEBUG + nsDOMWorker** newWorkers = +#endif + aArray.AppendElements(mWorkers); + NS_WARN_IF_FALSE(newWorkers, "Out of memory!"); +} + +void +nsDOMWorkerPool::Cancel() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(!mCanceled, "Canceled more than once!"); + + nsAutoTArray workers; + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + mCanceled = PR_TRUE; + + GetWorkers(workers); + } + + PRUint32 count = workers.Length(); + if (count) { + for (PRUint32 index = 0; index < count; index++) { + workers[index]->Cancel(); + } + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + mon.NotifyAll(); + } +} + +void +nsDOMWorkerPool::Suspend() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + nsAutoTArray workers; + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + NS_ASSERTION(!mSuspended, "Suspended more than once!"); + mSuspended = PR_TRUE; + + GetWorkers(workers); + } + + PRUint32 count = workers.Length(); + for (PRUint32 index = 0; index < count; index++) { + workers[index]->Suspend(); + } +} + +void +nsDOMWorkerPool::Resume() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + nsAutoTArray workers; + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + NS_ASSERTION(mSuspended, "Not suspended!"); + mSuspended = PR_FALSE; + + GetWorkers(workers); + } + + PRUint32 count = workers.Length(); + if (count) { + for (PRUint32 index = 0; index < count; index++) { + workers[index]->Resume(); + } + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + mon.NotifyAll(); + } +} diff --git a/dom/src/threads/nsDOMWorkerPool.h b/dom/src/threads/nsDOMWorkerPool.h new file mode 100644 index 000000000000..16a81f6c99d4 --- /dev/null +++ b/dom/src/threads/nsDOMWorkerPool.h @@ -0,0 +1,118 @@ +/* -*- 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): + * Vladimir Vukicevic (Original Author) + * Ben Turner + * + * 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 __NSDOMWORKERPOOL_H__ +#define __NSDOMWORKERPOOL_H__ + +// Other includes +#include "jsapi.h" +#include "mozilla/ReentrantMonitor.h" +#include "nsCOMPtr.h" +#include "nsStringGlue.h" +#include "nsTArray.h" + +class nsDOMWorker; +class nsIDocument; +class nsIScriptContext; +class nsIScriptError; +class nsIScriptGlobalObject; + +class nsDOMWorkerPool +{ + typedef mozilla::ReentrantMonitor ReentrantMonitor; + +public: + nsDOMWorkerPool(nsIScriptGlobalObject* aGlobalObject, + nsIDocument* aDocument); + + NS_IMETHOD_(nsrefcnt) AddRef(); + NS_IMETHOD_(nsrefcnt) Release(); + + nsIScriptGlobalObject* ScriptGlobalObject() { + return mParentGlobal; + } + + nsIDocument* ParentDocument() { + return mParentDocument; + } + + nsresult Init(); + + void Cancel(); + void Suspend(); + void Resume(); + + nsresult NoteWorker(nsDOMWorker* aWorker); + void NoteDyingWorker(nsDOMWorker* aWorker); + + ReentrantMonitor& GetReentrantMonitor() { + return mReentrantMonitor; + } + + const PRUint64 WindowID() const { + return mWindowID; + } + +private: + virtual ~nsDOMWorkerPool(); + + void GetWorkers(nsTArray& aArray); + + nsAutoRefCnt mRefCnt; + + // Reference to the window that created and owns this pool. + nsCOMPtr mParentGlobal; + + // Reference to the document that created this pool. + nsCOMPtr mParentDocument; + + // Weak array of workers. The idea is that workers can be garbage collected + // independently of the owning pool and other workers. + nsTArray mWorkers; + + // ReentrantMonitor for suspending and resuming workers. + ReentrantMonitor mReentrantMonitor; + + PRPackedBool mCanceled; + PRPackedBool mSuspended; + + const PRUint64 mWindowID; +}; + +#endif /* __NSDOMWORKERPOOL_H__ */ diff --git a/dom/src/threads/nsDOMWorkerScriptLoader.cpp b/dom/src/threads/nsDOMWorkerScriptLoader.cpp new file mode 100644 index 000000000000..e1353ea6f960 --- /dev/null +++ b/dom/src/threads/nsDOMWorkerScriptLoader.cpp @@ -0,0 +1,881 @@ +/* -*- 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 (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 "nsIChannel.h" +#include "nsIContentPolicy.h" +#include "nsIHttpChannel.h" +#include "nsIIOService.h" +#include "nsIProtocolHandler.h" +#include "nsIRequest.h" +#include "nsIScriptSecurityManager.h" +#include "nsIStreamLoader.h" + +// Other includes +#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" +#include "nsDocShellCID.h" +#include "nsIChannelPolicy.h" +#include "nsChannelPolicy.h" +#include "nsIContentSecurityPolicy.h" + +// DOMWorker includes +#include "nsDOMWorkerPool.h" +#include "nsDOMWorkerSecurityManager.h" +#include "nsDOMThreadService.h" +#include "nsDOMWorkerTimeout.h" + +using namespace mozilla; + +#define LOG(_args) PR_LOG(gDOMThreadsLog, PR_LOG_DEBUG, _args) + +nsDOMWorkerScriptLoader::nsDOMWorkerScriptLoader(nsDOMWorker* aWorker) +: nsDOMWorkerFeature(aWorker), + mTarget(nsnull), + mScriptCount(0), + mCanceled(PR_FALSE), + mForWorker(PR_FALSE) +{ + // Created on worker thread. + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(aWorker, "Null worker!"); +} + +NS_IMPL_ISUPPORTS_INHERITED2(nsDOMWorkerScriptLoader, nsDOMWorkerFeature, + nsIRunnable, + nsIStreamLoaderObserver) + +nsresult +nsDOMWorkerScriptLoader::LoadScripts(JSContext* aCx, + const nsTArray& aURLs, + PRBool aExecute) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(aCx, "Null context!"); + + mTarget = NS_GetCurrentThread(); + NS_ASSERTION(mTarget, "This should never be null!"); + + 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(aCx); + if (NS_FAILED(rv)) { + return rv; + } + + // Verify that all scripts downloaded and compiled. + rv = VerifyScripts(aCx); + if (NS_FAILED(rv)) { + return rv; + } + + if (aExecute) { + rv = ExecuteScripts(aCx); + if (NS_FAILED(rv)) { + return rv; + } + } + + return NS_OK; +} + +nsresult +nsDOMWorkerScriptLoader::LoadWorkerScript(JSContext* aCx, + const nsString& aURL) +{ + mForWorker = PR_TRUE; + + nsAutoTArray url; + url.AppendElement(aURL); + + return LoadScripts(aCx, url, PR_FALSE); +} + +nsresult +nsDOMWorkerScriptLoader::DoRunLoop(JSContext* aCx) +{ + 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); + + while (!(done || mCanceled)) { + JSAutoSuspendRequest asr(aCx); + NS_ProcessNextEvent(mTarget); + } + + return mCanceled ? NS_ERROR_ABORT : NS_OK; +} + +nsresult +nsDOMWorkerScriptLoader::VerifyScripts(JSContext* aCx) +{ + NS_ASSERTION(aCx, "Shouldn't be null!"); + + 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.ToJSObject()) { + continue; + } + + NS_ASSERTION(!loadInfo.scriptObj.ToJSObject(), "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. + + JSAutoRequest ar(aCx); + + // Only throw an error if there is no other pending exception. + if (!JS_IsExceptionPending(aCx)) { + const char* message; + switch (loadInfo.result) { + case NS_ERROR_MALFORMED_URI: + message = "Malformed script URI: %s"; + break; + case NS_ERROR_FILE_NOT_FOUND: + case NS_ERROR_NOT_AVAILABLE: + message = "Script file not found: %s"; + break; + default: + message = "Failed to load script: %s (nsresult = 0x%x)"; + break; + } + NS_ConvertUTF16toUTF8 url(loadInfo.url); + JS_ReportError(aCx, message, url.get(), loadInfo.result); + } + break; + } + + return rv; +} + +nsresult +nsDOMWorkerScriptLoader::ExecuteScripts(JSContext* aCx) +{ + NS_ASSERTION(aCx, "Shouldn't be null!"); + + // Now execute all the scripts. + for (PRUint32 index = 0; index < mScriptCount; index++) { + ScriptLoadInfo& loadInfo = mLoadInfos[index]; + + JSAutoRequest ar(aCx); + + JSObject* scriptObj = loadInfo.scriptObj.ToJSObject(); + NS_ASSERTION(scriptObj, "This shouldn't ever be null!"); + + JSObject* global = mWorker->mGlobal ? + mWorker->mGlobal : + JS_GetGlobalObject(aCx); + 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(aCx, JS_GetOptions(aCx) | JSOPTION_DONT_REPORT_UNCAUGHT); + + PRBool success = JS_ExecuteScript(aCx, global, scriptObj, NULL); + + JS_SetOptions(aCx, 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(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 runnables; + { + MutexAutoLock lock(mWorker->GetLock()); + 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(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!"); + + if (mForWorker) { + NS_ASSERTION(mScriptCount == 1, "Bad state!"); + } + + nsRefPtr parentWorker = mWorker->GetParent(); + + // Figure out which principal to use. + nsIPrincipal* principal = mWorker->GetPrincipal(); + if (!principal) { + if (!parentWorker) { + NS_ERROR("Must have a principal if this is not a subworker!"); + } + principal = parentWorker->GetPrincipal(); + } + NS_ASSERTION(principal, "This should never be null here!"); + + // Figure out our base URI. + nsCOMPtr baseURI; + if (mForWorker) { + if (parentWorker) { + baseURI = parentWorker->GetBaseURI(); + NS_ASSERTION(baseURI, "Should have been set already!"); + } + else { + // May be null. + baseURI = mWorker->GetBaseURI(); + + // Don't leave a temporary URI hanging around. + mWorker->ClearBaseURI(); + } + NS_ASSERTION(!mWorker->GetBaseURI(), "Should not be set here!"); + } + else { + baseURI = mWorker->GetBaseURI(); + NS_ASSERTION(baseURI, "Should have been set already!"); + } + + nsCOMPtr parentDoc = mWorker->Pool()->ParentDocument(); + + // All of these can potentially be null, but that should be ok. We'll either + // succeed without them or fail below. + nsCOMPtr loadGroup; + if (parentDoc) { + loadGroup = parentDoc->GetDocumentLoadGroup(); + } + + nsCOMPtr ios(do_GetIOService()); + + nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); + NS_ASSERTION(secMan, "This should never be null!"); + + for (PRUint32 index = 0; index < mScriptCount; index++) { + ScriptLoadInfo& loadInfo = mLoadInfos[index]; + nsresult& rv = loadInfo.result; + + nsCOMPtr& uri = loadInfo.finalURI; + rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), + loadInfo.url, parentDoc, + baseURI); + if (NS_FAILED(rv)) { + return rv; + } + + // If we're part of a document then check the content load policy. + if (parentDoc) { + PRInt16 shouldLoad = nsIContentPolicy::ACCEPT; + rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_SCRIPT, uri, + principal, 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 (rv = NS_ERROR_CONTENT_BLOCKED); + } + return (rv = NS_ERROR_CONTENT_BLOCKED_SHOW_ALT); + } + } + + // If this script loader is being used to make a new worker then we need to + // do a same-origin check. Otherwise we need to clear the load with the + // security manager. + rv = mForWorker ? + principal->CheckMayLoad(uri, PR_FALSE): + secMan->CheckLoadURIWithPrincipal(principal, uri, 0); + NS_ENSURE_SUCCESS(rv, rv); + + // We need to know which index we're on in OnStreamComplete so we know where + // to put the result. + nsCOMPtr 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 loader; + rv = NS_NewStreamLoader(getter_AddRefs(loader), this); + NS_ENSURE_SUCCESS(rv, rv); + + // Get Content Security Policy from parent document to pass into channel + nsCOMPtr csp; + rv = principal->GetCsp(getter_AddRefs(csp)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr channelPolicy; + if (csp) { + channelPolicy = do_CreateInstance("@mozilla.org/nschannelpolicy;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = channelPolicy->SetContentSecurityPolicy(csp); + NS_ENSURE_SUCCESS(rv, rv); + + rv = channelPolicy->SetLoadType(nsIContentPolicy::TYPE_SCRIPT); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = NS_NewChannel(getter_AddRefs(loadInfo.channel), + uri, ios, loadGroup, nsnull, + nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI, + channelPolicy); + 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 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; + + // Use an alias to keep rv and loadInfo.result in sync. + nsresult& rv = loadInfo.result; + + if (NS_FAILED(aStatus)) { + return rv = aStatus; + } + + if (!(aStringLen && aString)) { + return rv = NS_ERROR_UNEXPECTED; + } + + // Make sure we're not seeing the result of a 404 or something by checking the + // 'requestSucceeded' attribute on the http channel. + nsCOMPtr request; + rv = aLoader->GetRequest(getter_AddRefs(request)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr httpChannel = do_QueryInterface(request); + if (httpChannel) { + PRBool requestSucceeded; + rv = httpChannel->GetRequestSucceeded(&requestSucceeded); + NS_ENSURE_SUCCESS(rv, rv); + + if (!requestSucceeded) { + return rv = NS_ERROR_NOT_AVAILABLE; + } + } + + // May be null. + nsIDocument* parentDoc = mWorker->Pool()->ParentDocument(); + + // 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; + rv = loadInfo.finalURI->GetSpec(filename); + NS_ENSURE_SUCCESS(rv, rv); + + 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)); + } + + // Update the principal of the worker and its base URI if we just loaded the + // worker's primary script. + if (mForWorker) { + nsCOMPtr channel = do_QueryInterface(request); + NS_ASSERTION(channel, "This should never fail!"); + + // Take care of the base URI first. + nsCOMPtr finalURI; + rv = NS_GetFinalChannelURI(channel, getter_AddRefs(finalURI)); + NS_ENSURE_SUCCESS(rv, rv); + + mWorker->SetBaseURI(finalURI); + + // Now to figure out which principal to give this worker. + nsRefPtr parent = mWorker->GetParent(); + NS_ASSERTION(mWorker->GetPrincipal() || parent, "Must have one of these!"); + + nsCOMPtr loadPrincipal = mWorker->GetPrincipal() ? + mWorker->GetPrincipal() : + parent->GetPrincipal(); + + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + NS_ASSERTION(ssm, "Should never be null!"); + + nsCOMPtr channelPrincipal; + rv = ssm->GetChannelPrincipal(channel, getter_AddRefs(channelPrincipal)); + NS_ENSURE_SUCCESS(rv, rv); + + // See if this is a resource URI. Since JSMs usually come from resource:// + // URIs we're currently considering all URIs with the URI_IS_UI_RESOURCE + // flag as valid for creating privileged workers. + if (!nsContentUtils::IsSystemPrincipal(channelPrincipal)) { + PRBool isResource; + rv = NS_URIChainHasFlags(finalURI, + nsIProtocolHandler::URI_IS_UI_RESOURCE, + &isResource); + NS_ENSURE_SUCCESS(rv, rv); + + if (isResource) { + rv = ssm->GetSystemPrincipal(getter_AddRefs(channelPrincipal)); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + // If the load principal is the system principal then the channel principal + // must also be the system principal (we do not allow chrome code to create + // workers with non-chrome scripts). Otherwise this channel principal must + // be same origin with the load principal (we check again here in case + // redirects changed the location of the script). + if (nsContentUtils::IsSystemPrincipal(loadPrincipal)) { + if (!nsContentUtils::IsSystemPrincipal(channelPrincipal)) { + return rv = NS_ERROR_DOM_BAD_URI; + } + } + else if (NS_FAILED(loadPrincipal->CheckMayLoad(finalURI, PR_FALSE))) { + return rv = NS_ERROR_DOM_BAD_URI; + } + + mWorker->SetPrincipal(channelPrincipal); + } + + nsRefPtr compiler = + new ScriptCompiler(this, 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->SuspendFeatures(); +} + +void +nsDOMWorkerScriptLoader::ResumeWorkerEvents() +{ + NS_ASSERTION(mWorker, "No worker yet!"); + mWorker->ResumeFeatures(); +} + +nsDOMWorkerScriptLoader:: +ScriptLoaderRunnable::ScriptLoaderRunnable(nsDOMWorkerScriptLoader* aLoader) +: mRevoked(PR_FALSE), + mLoader(aLoader) +{ + MutexAutoLock lock(aLoader->GetLock()); +#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) { + MutexAutoLock lock(mLoader->GetLock()); +#ifdef DEBUG + PRBool removed = +#endif + mLoader->mPendingRunnables.RemoveElement(this); + NS_ASSERTION(removed, "Someone has changed the array!"); + } +} + +NS_IMPL_THREADSAFE_ISUPPORTS1(nsDOMWorkerScriptLoader::ScriptLoaderRunnable, + nsIRunnable) + +void +nsDOMWorkerScriptLoader::ScriptLoaderRunnable::Revoke() +{ + mRevoked = PR_TRUE; +} + +nsDOMWorkerScriptLoader:: +ScriptCompiler::ScriptCompiler(nsDOMWorkerScriptLoader* aLoader, + const nsString& aScriptText, + const nsCString& aFilename, + nsAutoJSValHolder& aScriptObj) +: ScriptLoaderRunnable(aLoader), + mScriptText(aScriptText), + mFilename(aFilename), + mScriptObj(aScriptObj) +{ + 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.ToJSObject(), "Already have a script object?!"); + NS_ASSERTION(mScriptObj.IsHeld(), "Not held?!"); + NS_ASSERTION(!mScriptText.IsEmpty(), "Shouldn't have empty source here!"); + + JSContext* cx = nsDOMThreadService::GetCurrentContext(); + NS_ENSURE_STATE(cx); + + JSAutoRequest ar(cx); + + JSObject* global = JS_GetGlobalObject(cx); + 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(cx, JS_GetOptions(cx) | JSOPTION_DONT_REPORT_UNCAUGHT | + JSOPTION_NO_SCRIPT_RVAL); + + JSPrincipals* principal = nsDOMWorkerSecurityManager::WorkerPrincipal(); + + JSObject* scriptObj = + JS_CompileUCScriptForPrincipals(cx, global, principal, + reinterpret_cast + (mScriptText.BeginReading()), + mScriptText.Length(), mFilename.get(), 1); + + JS_SetOptions(cx, oldOpts); + + if (!scriptObj) { + return NS_ERROR_FAILURE; + } + + mScriptObj = scriptObj; + + 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(); +} diff --git a/dom/src/threads/nsDOMWorkerScriptLoader.h b/dom/src/threads/nsDOMWorkerScriptLoader.h new file mode 100644 index 000000000000..4332958d5375 --- /dev/null +++ b/dom/src/threads/nsDOMWorkerScriptLoader.h @@ -0,0 +1,225 @@ +/* -*- 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 (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 "nsIRunnable.h" +#include "nsIStreamLoader.h" + +// Interfaces +#include "nsIChannel.h" +#include "nsIURI.h" + +// Other includes +#include "jsapi.h" +#include "nsAutoPtr.h" +#include "nsAutoJSValHolder.h" +#include "nsCOMPtr.h" +#include "nsStringGlue.h" +#include "nsTArray.h" + +#include "nsDOMWorker.h" + +class nsIThread; + +/** + * 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 nsDOMWorkerFeature, + public nsIRunnable, + public nsIStreamLoaderObserver +{ + typedef mozilla::Mutex Mutex; + + friend class AutoSuspendWorkerEvents; + friend class ScriptLoaderRunnable; + +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIRUNNABLE + NS_DECL_NSISTREAMLOADEROBSERVER + + nsDOMWorkerScriptLoader(nsDOMWorker* aWorker); + + nsresult LoadScripts(JSContext* aCx, + const nsTArray& aURLs, + PRBool aExecute); + + nsresult LoadWorkerScript(JSContext* aCx, + const nsString& aURL); + + nsresult ExecuteScripts(JSContext* aCx); + + virtual void Cancel(); + +private: + ~nsDOMWorkerScriptLoader() { } + + + nsresult DoRunLoop(JSContext* aCx); + nsresult VerifyScripts(JSContext* aCx); + + nsresult RunInternal(); + + nsresult OnStreamCompleteInternal(nsIStreamLoader* aLoader, + nsISupports* aContext, + nsresult aStatus, + PRUint32 aStringLen, + const PRUint8* aString); + + void NotifyDone(); + + void SuspendWorkerEvents(); + void ResumeWorkerEvents(); + + Mutex& GetLock() { + return mWorker->GetLock(); + } + + class ScriptLoaderRunnable : public nsIRunnable + { + public: + NS_DECL_ISUPPORTS + + 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, + const nsString& aScriptText, + const nsCString& aFilename, + nsAutoJSValHolder& aScriptObj); + + private: + nsString mScriptText; + nsCString mFilename; + nsAutoJSValHolder& 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 finalURI; + nsCOMPtr channel; + nsAutoJSValHolder scriptObj; + }; + + nsIThread* mTarget; + + nsRefPtr mDoneRunnable; + + PRUint32 mScriptCount; + nsTArray mLoadInfos; + + // Protected by mWorker's lock! + nsTArray mPendingRunnables; + + PRPackedBool mCanceled; + PRPackedBool mForWorker; +}; + +#endif /* __NSDOMWORKERSCRIPTLOADER_H__ */ diff --git a/dom/src/threads/nsDOMWorkerSecurityManager.cpp b/dom/src/threads/nsDOMWorkerSecurityManager.cpp new file mode 100644 index 000000000000..5af623556b8a --- /dev/null +++ b/dom/src/threads/nsDOMWorkerSecurityManager.cpp @@ -0,0 +1,148 @@ +/* -*- 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 (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 "nsDOMWorkerSecurityManager.h" + +// Interfaces +#include "nsIClassInfo.h" + +// Other includes +#include "jsapi.h" +#include "nsDOMError.h" +#include "nsThreadUtils.h" + +// DOMWorker includes +#include "nsDOMThreadService.h" +#include "nsDOMWorker.h" + +#define LOG(_args) PR_LOG(gDOMThreadsLog, PR_LOG_DEBUG, _args) + +class nsDOMWorkerPrincipal +{ +public: + static void Destroy(JSContext*, JSPrincipals*) { + // nothing + } + + static JSBool Subsume(JSPrincipals*, JSPrincipals*) { + return JS_TRUE; + } +}; + +static JSPrincipals gWorkerPrincipal = +{ "domworkerthread" /* codebase */, + NULL /* getPrincipalArray */, + NULL /* globalPrivilegesEnabled */, + 1 /* refcount */, + nsDOMWorkerPrincipal::Destroy /* destroy */, + nsDOMWorkerPrincipal::Subsume /* subsume */ }; + +NS_IMPL_THREADSAFE_ISUPPORTS1(nsDOMWorkerSecurityManager, + nsIXPCSecurityManager) + +NS_IMETHODIMP +nsDOMWorkerSecurityManager::CanCreateWrapper(JSContext* aCx, + const nsIID& aIID, + nsISupports* aObj, + nsIClassInfo* aClassInfo, + void** aPolicy) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerSecurityManager::CanCreateInstance(JSContext* aCx, + const nsCID& aCID) +{ + return CanGetService(aCx, aCID); +} + +NS_IMETHODIMP +nsDOMWorkerSecurityManager::CanGetService(JSContext* aCx, + const nsCID& aCID) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + nsDOMWorker* worker = static_cast(JS_GetContextPrivate(aCx)); + NS_ASSERTION(worker, "This should be set by the DOM thread service!"); + + return worker->IsPrivileged() ? NS_OK : NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED; +} + +NS_IMETHODIMP +nsDOMWorkerSecurityManager::CanAccess(PRUint32 aAction, + nsAXPCNativeCallContext* aCallContext, + JSContext* aJSContext, + JSObject* aJSObject, + nsISupports* aObj, + nsIClassInfo* aClassInfo, + jsid aName, + void** aPolicy) +{ + return NS_OK; +} + +JSPrincipals* +nsDOMWorkerSecurityManager::WorkerPrincipal() +{ + return &gWorkerPrincipal; +} + +JSBool +nsDOMWorkerSecurityManager::JSCheckAccess(JSContext* aCx, + JSObject* aObj, + jsid aId, + JSAccessMode aMode, + jsval* aVp) +{ + return JS_TRUE; +} + +JSPrincipals* +nsDOMWorkerSecurityManager::JSFindPrincipal(JSContext* aCx, JSObject* aObj) +{ + return WorkerPrincipal(); +} + +JSBool +nsDOMWorkerSecurityManager::JSTranscodePrincipals(JSXDRState* aXdr, + JSPrincipals** aJsprinp) +{ + NS_NOTREACHED("Shouldn't ever call this!"); + return JS_FALSE; +} diff --git a/dom/workers/XMLHttpRequest.h b/dom/src/threads/nsDOMWorkerSecurityManager.h similarity index 67% rename from dom/workers/XMLHttpRequest.h rename to dom/src/threads/nsDOMWorkerSecurityManager.h index c14d593b43b2..bfa16b157094 100644 --- a/dom/workers/XMLHttpRequest.h +++ b/dom/src/threads/nsDOMWorkerSecurityManager.h @@ -1,4 +1,4 @@ -/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* -*- 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 * @@ -12,11 +12,11 @@ * for the specific language governing rights and limitations under the * License. * - * The Original Code is Web Workers. + * The Original Code is worker threads. * * The Initial Developer of the Original Code is - * The Mozilla Foundation. - * Portions created by the Initial Developer are Copyright (C) 2011 + * Mozilla Corporation. + * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): @@ -36,37 +36,27 @@ * * ***** END LICENSE BLOCK ***** */ -#ifndef mozilla_dom_workers_xmlhttprequest_h__ -#define mozilla_dom_workers_xmlhttprequest_h__ +#ifndef __NSDOMWORKERSECURITYMANAGER_H__ +#define __NSDOMWORKERSECURITYMANAGER_H__ -#include "Workers.h" +#include "nsIXPCSecurityManager.h" +#include "jsapi.h" -#include "jspubtd.h" - -BEGIN_WORKERS_NAMESPACE - -namespace xhr { - -bool -InitClasses(JSContext* aCx, JSObject* aGlobal, JSObject* aProto); - -struct StateData +class nsDOMWorkerSecurityManager : public nsIXPCSecurityManager { - jsval mResponseText; - jsval mStatus; - jsval mStatusText; - jsval mReadyState; - bool mResponseTextException; - bool mStatusException; - bool mStatusTextException; - bool mReadyStateException; +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCSECURITYMANAGER + + static JSPrincipals* WorkerPrincipal(); + + static JSBool JSCheckAccess(JSContext* aCx, JSObject* aObj, jsid aId, + JSAccessMode aMode, jsval* aVp); + + static JSPrincipals* JSFindPrincipal(JSContext* aCx, JSObject* aObj); + + static JSBool JSTranscodePrincipals(JSXDRState* aXdr, + JSPrincipals** aJsprinp); }; -bool -UpdateXHRState(JSContext* aCx, JSObject* aObj, const StateData& aNewState); - -} // namespace xhr - -END_WORKERS_NAMESPACE - -#endif /* mozilla_dom_workers_xmlhttprequest_h__ */ +#endif /* __NSDOMWORKERSECURITYMANAGER_H__ */ diff --git a/dom/src/threads/nsDOMWorkerTimeout.cpp b/dom/src/threads/nsDOMWorkerTimeout.cpp new file mode 100644 index 000000000000..303af939d14d --- /dev/null +++ b/dom/src/threads/nsDOMWorkerTimeout.cpp @@ -0,0 +1,485 @@ +/* -*- 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 (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 "nsDOMWorkerTimeout.h" + +// Interfaces +#include "nsIJSContextStack.h" +#include "nsIJSRuntimeService.h" +#include "nsITimer.h" +#include "nsIXPConnect.h" + +// Other includes +#include "nsContentUtils.h" +#include "nsJSUtils.h" +#include "nsThreadUtils.h" +#include "pratom.h" +#include "prtime.h" + +// DOMWorker includes +#include "nsDOMThreadService.h" +#include "nsDOMWorkerSecurityManager.h" + +#define LOG(_args) PR_LOG(gDOMThreadsLog, PR_LOG_DEBUG, _args) + +#define CONSTRUCTOR_ENSURE_TRUE(_cond, _rv) \ + PR_BEGIN_MACRO \ + if (NS_UNLIKELY(!(_cond))) { \ + NS_WARNING("CONSTRUCTOR_ENSURE_TRUE(" #_cond ") failed"); \ + (_rv) = NS_ERROR_FAILURE; \ + return; \ + } \ + PR_END_MACRO + +#define SUSPEND_SPINLOCK_COUNT 5000 + +static const char* kSetIntervalStr = "setInterval"; +static const char* kSetTimeoutStr = "setTimeout"; + +nsDOMWorkerTimeout::FunctionCallback::FunctionCallback(PRUint32 aArgc, + jsval* aArgv, + nsresult* aRv) +: mCallbackArgsLength(0) +{ + MOZ_COUNT_CTOR(nsDOMWorkerTimeout::FunctionCallback); + + JSRuntime* rt; + *aRv = nsDOMThreadService::JSRuntimeService()->GetRuntime(&rt); + if (NS_FAILED(*aRv)) + return; + + JSBool ok = mCallback.Hold(rt); + CONSTRUCTOR_ENSURE_TRUE(ok, *aRv); + + mCallback = aArgv[0]; + + // We want enough space for an extra lateness arg. + mCallbackArgsLength = aArgc > 2 ? aArgc - 1 : 1; + + PRBool success = mCallbackArgs.SetLength(mCallbackArgsLength); + CONSTRUCTOR_ENSURE_TRUE(success, *aRv); + + PRUint32 index = 0; + for (; index < mCallbackArgsLength - 1; index++) { + ok = mCallbackArgs[index].Hold(rt); + CONSTRUCTOR_ENSURE_TRUE(ok, *aRv); + + mCallbackArgs[index] = aArgv[index + 2]; + } + + // Take care of the last arg. + index = mCallbackArgsLength - 1; + + ok = mCallbackArgs[index].Hold(rt); + CONSTRUCTOR_ENSURE_TRUE(ok, *aRv); + + *aRv = NS_OK; +} + +nsDOMWorkerTimeout::FunctionCallback::~FunctionCallback() +{ + MOZ_COUNT_DTOR(nsDOMWorkerTimeout::FunctionCallback); +} + +nsresult +nsDOMWorkerTimeout::FunctionCallback::Run(nsDOMWorkerTimeout* aTimeout, + JSContext* aCx) +{ + PRInt32 lateness = NS_MAX(0, PRInt32(PR_Now() - aTimeout->mTargetTime)) / + (PRTime)PR_USEC_PER_MSEC; + mCallbackArgs[mCallbackArgsLength - 1] = INT_TO_JSVAL(lateness); + + JSObject* global = JS_GetGlobalObject(aCx); + NS_ENSURE_TRUE(global, NS_ERROR_FAILURE); + + nsTArray argv; + PRBool success = argv.SetCapacity(mCallbackArgsLength); + NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY); + + for (PRUint32 index = 0; index < mCallbackArgsLength; index++) { + argv.AppendElement(mCallbackArgs[index]); + } + + jsval rval; + JSBool ok = + JS_CallFunctionValue(aCx, global, mCallback, mCallbackArgsLength, + argv.Elements(), &rval); + NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); + + return NS_OK; +} + +nsDOMWorkerTimeout::ExpressionCallback::ExpressionCallback(PRUint32 aArgc, + jsval* aArgv, + JSContext* aCx, + nsresult* aRv) +: mLineNumber(0) +{ + MOZ_COUNT_CTOR(nsDOMWorkerTimeout::ExpressionCallback); + + JSString* expr = JS_ValueToString(aCx, aArgv[0]); + *aRv = expr ? NS_OK : NS_ERROR_FAILURE; + if (NS_FAILED(*aRv)) + return; + + JSRuntime* rt; + *aRv = nsDOMThreadService::JSRuntimeService()->GetRuntime(&rt); + if (NS_FAILED(*aRv)) + return; + + JSBool ok = mExpression.Hold(rt); + CONSTRUCTOR_ENSURE_TRUE(ok, *aRv); + + mExpression = aArgv[0]; + + // Get the calling location. + const char* fileName; + PRUint32 lineNumber; + if (nsJSUtils::GetCallingLocation(aCx, &fileName, &lineNumber)) { + mFileName.Assign(fileName); + mLineNumber = lineNumber; + } + + *aRv = NS_OK; +} + +nsDOMWorkerTimeout::ExpressionCallback::~ExpressionCallback() +{ + MOZ_COUNT_DTOR(nsDOMWorkerTimeout::ExpressionCallback); +} + +nsresult +nsDOMWorkerTimeout::ExpressionCallback::Run(nsDOMWorkerTimeout* aTimeout, + JSContext* aCx) +{ + JSObject* global = JS_GetGlobalObject(aCx); + NS_ENSURE_TRUE(global, NS_ERROR_FAILURE); + + JSPrincipals* principal = nsDOMWorkerSecurityManager::WorkerPrincipal(); + NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE); + + JSString* expression = JS_ValueToString(aCx, mExpression); + NS_ENSURE_TRUE(expression, NS_ERROR_FAILURE); + + size_t stringLength; + const jschar* string = JS_GetStringCharsAndLength(aCx, expression, &stringLength); + NS_ENSURE_TRUE(string, NS_ERROR_FAILURE); + + PRBool success = JS_EvaluateUCScriptForPrincipals(aCx, global, principal, + string, stringLength, + mFileName.get(), + mLineNumber, nsnull); + if (!success) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsDOMWorkerTimeout::nsDOMWorkerTimeout(nsDOMWorker* aWorker, + PRUint32 aId) +: nsDOMWorkerFeature(aWorker, aId), + mInterval(0), + mSuspendSpinlock(0), + mSuspendInterval(0), + mIsInterval(PR_FALSE), + mIsSuspended(PR_FALSE), + mSuspendedBeforeStart(PR_FALSE), + mStarted(PR_FALSE) +{ + NS_ASSERTION(mWorker, "Need a worker here!"); +} + +NS_IMPL_ISUPPORTS_INHERITED1(nsDOMWorkerTimeout, nsDOMWorkerFeature, + nsITimerCallback) + +nsresult +nsDOMWorkerTimeout::Init(JSContext* aCx, PRUint32 aArgc, jsval* aArgv, + PRBool aIsInterval) +{ + NS_ASSERTION(aCx, "Null pointer!"); + NS_ASSERTION(aArgv, "Null pointer!"); + + JSAutoRequest ar(aCx); + + if (!aArgc) { + JS_ReportError(aCx, "Function %s requires at least 1 parameter", + aIsInterval ? kSetIntervalStr : kSetTimeoutStr); + return NS_ERROR_INVALID_ARG; + } + + PRUint32 interval; + if (aArgc > 1) { + if (!JS_ValueToECMAUint32(aCx, aArgv[1], (uint32*)&interval)) { + JS_ReportError(aCx, "Second argument to %s must be a millisecond value", + aIsInterval ? kSetIntervalStr : kSetTimeoutStr); + return NS_ERROR_INVALID_ARG; + } + } + else { + // If no interval was specified, treat this like a timeout, to avoid + // setting an interval of 0 milliseconds. + interval = 0; + aIsInterval = PR_FALSE; + } + + mInterval = interval; + + mIsInterval = aIsInterval; + + mTargetTime = PR_Now() + interval * (PRTime)PR_USEC_PER_MSEC; + + nsresult rv; + switch (JS_TypeOfValue(aCx, aArgv[0])) { + case JSTYPE_FUNCTION: + mCallback = new FunctionCallback(aArgc, aArgv, &rv); + NS_ENSURE_TRUE(mCallback, NS_ERROR_OUT_OF_MEMORY); + NS_ENSURE_SUCCESS(rv, rv); + + break; + + case JSTYPE_STRING: + case JSTYPE_OBJECT: + mCallback = new ExpressionCallback(aArgc, aArgv, aCx, &rv); + NS_ENSURE_TRUE(mCallback, NS_ERROR_OUT_OF_MEMORY); + NS_ENSURE_SUCCESS(rv, rv); + break; + + default: + JS_ReportError(aCx, "useless %s call (missing quotes around argument?)", + aIsInterval ? kSetIntervalStr : kSetTimeoutStr); + + // Return an error that nsGlobalWindow can recognize and turn into NS_OK. + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr timer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsIEventTarget* target = + static_cast(nsDOMThreadService::get()); + + rv = timer->SetTarget(target); + NS_ENSURE_SUCCESS(rv, rv); + + mTimer.swap(timer); + return NS_OK; +} + +nsresult +nsDOMWorkerTimeout::Start() +{ + if (IsSuspended()) { + NS_ASSERTION(mSuspendedBeforeStart, "Bad state!"); + return NS_OK; + } + + nsresult rv = mTimer->InitWithCallback(this, mInterval, + nsITimer::TYPE_ONE_SHOT); + NS_ENSURE_SUCCESS(rv, rv); + + mStarted = PR_TRUE; + return NS_OK; +} + +nsresult +nsDOMWorkerTimeout::Run() +{ + NS_ENSURE_TRUE(mCallback, NS_ERROR_NOT_INITIALIZED); + LOG(("Worker [0x%p] running timeout [0x%p] with id %u", + static_cast(mWorker.get()), static_cast(this), mId)); + + JSContext* cx; + nsresult rv = + nsDOMThreadService::ThreadJSContextStack()->GetSafeJSContext(&cx); + NS_ENSURE_SUCCESS(rv, rv); + + JSAutoRequest ar(cx); + + rv = mCallback->Run(this, cx); + + // Make sure any pending exceptions are converted to errors for the pool. + JS_ReportPendingException(cx); + + if (mIsInterval) { + mTargetTime = PR_Now() + mInterval * (PRTime)PR_USEC_PER_MSEC; + nsresult rv2 = mTimer->InitWithCallback(this, mInterval, + nsITimer::TYPE_ONE_SHOT); + NS_ENSURE_SUCCESS(rv2, rv2); + } + + return rv; +} + +void +nsDOMWorkerTimeout::Cancel() +{ + NS_ASSERTION(mTimer, "Impossible to get here without a timer!"); + + LOG(("Worker [0x%p] canceling timeout [0x%p] with id %u", + static_cast(mWorker.get()), static_cast(this), mId)); + + { + AutoSpinlock lock(this); + + if (IsSuspendedNoLock()) { + mIsSuspended = PR_FALSE; + // This should kill us when all is said and done. + mSuspendedRef = nsnull; + } + } + + // This call to Cancel should kill us. + mTimer->Cancel(); +} + +void +nsDOMWorkerTimeout::Suspend() +{ + AutoSpinlock lock(this); + + NS_ASSERTION(!IsSuspendedNoLock(), "Bad state!"); + + mIsSuspended = PR_TRUE; + mSuspendedRef = this; + + if (!mStarted) { + mSuspendedBeforeStart = PR_TRUE; + return; + } + + mTimer->Cancel(); + + mSuspendInterval = NS_MAX(0, PRInt32(mTargetTime - PR_Now())) / + (PRTime)PR_USEC_PER_MSEC; + + LOG(("Worker [0x%p] suspending timeout [0x%p] with id %u (interval = %u)", + static_cast(mWorker.get()), static_cast(this), mId, + mSuspendInterval)); +} + +void +nsDOMWorkerTimeout::Resume() +{ + NS_ASSERTION(mTimer, "Impossible to get here without a timer!"); + + LOG(("Worker [0x%p] resuming timeout [0x%p] with id %u", + static_cast(mWorker.get()), static_cast(this), mId)); + + AutoSpinlock lock(this); + + NS_ASSERTION(IsSuspendedNoLock(), "Should be suspended!"); + + if (mSuspendedBeforeStart) { + NS_ASSERTION(!mSuspendInterval, "Bad state!"); + mSuspendedBeforeStart = PR_FALSE; + mSuspendInterval = mInterval; + mStarted = PR_TRUE; + } + + mTargetTime = PR_Now() + mSuspendInterval * (PRTime)PR_USEC_PER_MSEC; + +#ifdef DEBUG + nsresult rv = +#endif + mTimer->InitWithCallback(this, mSuspendInterval, nsITimer::TYPE_ONE_SHOT); + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to init timer!"); +} + +void +nsDOMWorkerTimeout::AcquireSpinlock() +{ + PRUint32 loopCount = 0; + while (PR_ATOMIC_SET(&mSuspendSpinlock, 1) == 1) { + if (++loopCount > SUSPEND_SPINLOCK_COUNT) { + LOG(("AcquireSpinlock taking too long (looped %u times), yielding.", + loopCount)); + loopCount = 0; + PR_Sleep(PR_INTERVAL_NO_WAIT); + } + } +#ifdef PR_LOGGING + if (loopCount) { + LOG(("AcquireSpinlock needed %u loops", loopCount)); + } +#endif +} + +void +nsDOMWorkerTimeout::ReleaseSpinlock() +{ +#ifdef DEBUG + PRInt32 suspended = +#endif + PR_ATOMIC_SET(&mSuspendSpinlock, 0); + NS_ASSERTION(suspended == 1, "Huh?!"); +} + +NS_IMETHODIMP +nsDOMWorkerTimeout::Notify(nsITimer* aTimer) +{ + // Should be on the timer thread. + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(aTimer == mTimer, "Wrong timer?!"); + + PRUint32 type; + nsresult rv = aTimer->GetType(&type); + NS_ENSURE_SUCCESS(rv, rv); + + // We only care about one-shot timers here because that may be the one that + // we set from Resume(). + if (type == nsITimer::TYPE_ONE_SHOT) { + AutoSpinlock lock(this); + if (mIsSuspended) { + mIsSuspended = PR_FALSE; + mSuspendedRef = nsnull; + if (mIsInterval) { + // This is the first fire since we resumed. Set our interval back to the + // real interval. + mTargetTime = PR_Now() + mInterval * (PRTime)PR_USEC_PER_MSEC; + + rv = aTimer->InitWithCallback(this, mInterval, + nsITimer::TYPE_REPEATING_SLACK); + NS_ENSURE_SUCCESS(rv, rv); + } + } + } + + nsDOMThreadService::get()->TimeoutReady(this); + return NS_OK; +} diff --git a/dom/src/threads/nsDOMWorkerTimeout.h b/dom/src/threads/nsDOMWorkerTimeout.h new file mode 100644 index 000000000000..fce7834da756 --- /dev/null +++ b/dom/src/threads/nsDOMWorkerTimeout.h @@ -0,0 +1,190 @@ +/* -*- 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 (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 __NSDOMWORKERTIMEOUT_H__ +#define __NSDOMWORKERTIMEOUT_H__ + +// Interfaces +#include "nsITimer.h" + +// Other includes +#include "jsapi.h" +#include "nsAutoJSValHolder.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsStringGlue.h" +#include "nsTArray.h" + +// DOMWorker includes +#include "nsDOMWorker.h" + +/** + * The nsDOMWorkerTimeout has a slightly complicated life cycle. It's created + * by an nsDOMWorker (or one of its JS context functions) and immediately takes + * a strong reference to the worker that created it. It does this so that the + * worker can't be collected while a timeout is outstanding. However, the worker + * needs a weak reference to the timeout so that it can be canceled if the + * worker is canceled (in the event that the page falls out of the fastback + * cache or the application is exiting, for instance). The only thing that holds + * the timeout alive is its mTimer via the nsITimerCallback interface. If the + * timer is single-shot and has run already or if the timer is canceled then + * this object should die. + */ +class nsDOMWorkerTimeout : public nsDOMWorkerFeature, + public nsITimerCallback +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSITIMERCALLBACK + + nsDOMWorkerTimeout(nsDOMWorker* aWorker, + PRUint32 aId); + + nsresult Init(JSContext* aCx, + PRUint32 aArgc, + jsval* aArgv, + PRBool aIsInterval); + + nsresult Start(); + + nsresult Run(); + + virtual void Cancel(); + virtual void Suspend(); + virtual void Resume(); + + PRIntervalTime GetInterval() { + return mInterval; + } + + nsDOMWorker* GetWorker() { + return mWorker; + } + + PRBool IsSuspended() { + AutoSpinlock lock(this); + return IsSuspendedNoLock(); + } + +private: + ~nsDOMWorkerTimeout() { } + + void AcquireSpinlock(); + void ReleaseSpinlock(); + + PRBool IsSuspendedNoLock() { + return mIsSuspended; + } + + class AutoSpinlock + { + public: + AutoSpinlock(nsDOMWorkerTimeout* aTimeout) + : mTimeout(aTimeout) { + aTimeout->AcquireSpinlock(); + } + + ~AutoSpinlock() { + mTimeout->ReleaseSpinlock(); + } + private: + nsDOMWorkerTimeout* mTimeout; + }; + + // We support two types of callbacks (functions and expressions) just like the + // normal window timeouts. Each type has its own member and rooting needs so + // we split them into two classes with a common base. + class CallbackBase + { + public: + virtual ~CallbackBase() { } + virtual nsresult Run(nsDOMWorkerTimeout* aTimeout, + JSContext* aCx) = 0; + }; + + class FunctionCallback : public CallbackBase + { + public: + FunctionCallback(PRUint32 aArgc, + jsval* aArgv, + nsresult* aRv); + virtual ~FunctionCallback(); + virtual nsresult Run(nsDOMWorkerTimeout* aTimeout, + JSContext* aCx); + protected: + nsAutoJSValHolder mCallback; + nsTArray mCallbackArgs; + PRUint32 mCallbackArgsLength; + }; + + class ExpressionCallback : public CallbackBase + { + public: + ExpressionCallback(PRUint32 aArgc, + jsval* aArgv, + JSContext* aCx, + nsresult* aRv); + virtual ~ExpressionCallback(); + virtual nsresult Run(nsDOMWorkerTimeout* aTimeout, + JSContext* aCx); + protected: + nsAutoJSValHolder mExpression; + nsCString mFileName; + PRUint32 mLineNumber; + }; + + // Hold this object alive! + nsCOMPtr mTimer; + + PRUint32 mInterval; + + PRTime mTargetTime; + + nsAutoPtr mCallback; + + PRInt32 mSuspendSpinlock; + PRUint32 mSuspendInterval; + nsRefPtr mSuspendedRef; + + PRPackedBool mIsInterval; + PRPackedBool mIsSuspended; + PRPackedBool mSuspendedBeforeStart; + PRPackedBool mStarted; +}; + +#endif /* __NSDOMWORKERTIMEOUT_H__ */ diff --git a/dom/src/threads/nsDOMWorkerXHR.cpp b/dom/src/threads/nsDOMWorkerXHR.cpp new file mode 100644 index 000000000000..3e8094da7f32 --- /dev/null +++ b/dom/src/threads/nsDOMWorkerXHR.cpp @@ -0,0 +1,910 @@ +/* -*- 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 (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 "nsDOMWorkerXHR.h" + +// Interfaces +#include "nsIDocument.h" +#include "nsIDOMEvent.h" +#include "nsIThread.h" +#include "nsIXPConnect.h" + +// Other includes +#include "nsAXPCNativeCallContext.h" +#include "nsComponentManagerUtils.h" +#include "nsContentUtils.h" +#include "nsIClassInfoImpl.h" +#include "nsJSUtils.h" +#include "nsThreadUtils.h" + +// DOMWorker includes +#include "nsDOMThreadService.h" +#include "nsDOMWorkerEvents.h" +#include "nsDOMWorkerPool.h" +#include "nsDOMWorkerXHRProxy.h" + +using namespace mozilla; + +// The list of event types that we support. This list and the defines based on +// it determine the sizes of the listener arrays in nsDOMWorkerXHRProxy. Make +// sure that any event types shared by both the XHR and Upload objects are +// together at the beginning of the list. Any changes made to this list may +// affect sMaxUploadEventTypes, so make sure that it is adjusted accordingly or +// things will break! +const char* const nsDOMWorkerXHREventTarget::sListenerTypes[] = { + // nsIXMLHttpRequestEventTarget listeners. + "abort", /* LISTENER_TYPE_ABORT */ + "error", /* LISTENER_TYPE_ERROR */ + "load", /* LISTENER_TYPE_LOAD */ + "loadstart", /* LISTENER_TYPE_LOADSTART */ + "progress", /* LISTENER_TYPE_PROGRESS */ + + // nsIXMLHttpRequest listeners. + "readystatechange", /* LISTENER_TYPE_READYSTATECHANGE */ + "loadend" +}; + +// This should always be set to the length of sListenerTypes. +const PRUint32 nsDOMWorkerXHREventTarget::sMaxXHREventTypes = + NS_ARRAY_LENGTH(nsDOMWorkerXHREventTarget::sListenerTypes); + +// This should be set to the index of the first event type that is *not* +// supported by the Upload object. +const PRUint32 nsDOMWorkerXHREventTarget::sMaxUploadEventTypes = + LISTENER_TYPE_READYSTATECHANGE; + +// Enforce the invariant that the upload object supports no more event types +// than the xhr object. +PR_STATIC_ASSERT(nsDOMWorkerXHREventTarget::sMaxXHREventTypes >= + nsDOMWorkerXHREventTarget::sMaxUploadEventTypes); + +NS_IMPL_ISUPPORTS_INHERITED1(nsDOMWorkerXHREventTarget, + nsDOMWorkerMessageHandler, + nsIXMLHttpRequestEventTarget) + +PRUint32 +nsDOMWorkerXHREventTarget::GetListenerTypeFromString(const nsAString& aString) +{ + for (PRUint32 index = 0; index < sMaxXHREventTypes; index++) { + if (aString.EqualsASCII(sListenerTypes[index])) { + return index; + } + } + return PR_UINT32_MAX; +} + +NS_IMETHODIMP +nsDOMWorkerXHREventTarget::GetOnabort(nsIDOMEventListener** aOnabort) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + NS_ENSURE_ARG_POINTER(aOnabort); + + nsAutoString type; + type.AssignASCII(sListenerTypes[LISTENER_TYPE_ABORT]); + + nsCOMPtr listener = GetOnXListener(type); + listener.forget(aOnabort); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHREventTarget::SetOnabort(nsIDOMEventListener* aOnabort) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + nsAutoString type; + type.AssignASCII(sListenerTypes[LISTENER_TYPE_ABORT]); + + return SetOnXListener(type, aOnabort); +} + +NS_IMETHODIMP +nsDOMWorkerXHREventTarget::GetOnerror(nsIDOMEventListener** aOnerror) +{ + NS_ENSURE_ARG_POINTER(aOnerror); + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + nsAutoString type; + type.AssignASCII(sListenerTypes[LISTENER_TYPE_ERROR]); + + nsCOMPtr listener = GetOnXListener(type); + listener.forget(aOnerror); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHREventTarget::SetOnerror(nsIDOMEventListener* aOnerror) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + nsAutoString type; + type.AssignASCII(sListenerTypes[LISTENER_TYPE_ERROR]); + + return SetOnXListener(type, aOnerror); +} + +NS_IMETHODIMP +nsDOMWorkerXHREventTarget::GetOnload(nsIDOMEventListener** aOnload) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + NS_ENSURE_ARG_POINTER(aOnload); + + nsAutoString type; + type.AssignASCII(sListenerTypes[LISTENER_TYPE_LOAD]); + + nsCOMPtr listener = GetOnXListener(type); + listener.forget(aOnload); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHREventTarget::SetOnload(nsIDOMEventListener* aOnload) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + nsAutoString type; + type.AssignASCII(sListenerTypes[LISTENER_TYPE_LOAD]); + + return SetOnXListener(type, aOnload); +} + +NS_IMETHODIMP +nsDOMWorkerXHREventTarget::GetOnloadstart(nsIDOMEventListener** aOnloadstart) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + NS_ENSURE_ARG_POINTER(aOnloadstart); + + nsAutoString type; + type.AssignASCII(sListenerTypes[LISTENER_TYPE_LOADSTART]); + + nsCOMPtr listener = GetOnXListener(type); + listener.forget(aOnloadstart); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHREventTarget::SetOnloadstart(nsIDOMEventListener* aOnloadstart) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + nsAutoString type; + type.AssignASCII(sListenerTypes[LISTENER_TYPE_LOADSTART]); + + return SetOnXListener(type, aOnloadstart); +} + +NS_IMETHODIMP +nsDOMWorkerXHREventTarget::GetOnprogress(nsIDOMEventListener** aOnprogress) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + NS_ENSURE_ARG_POINTER(aOnprogress); + + nsAutoString type; + type.AssignASCII(sListenerTypes[LISTENER_TYPE_PROGRESS]); + + nsCOMPtr listener = GetOnXListener(type); + listener.forget(aOnprogress); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHREventTarget::SetOnprogress(nsIDOMEventListener* aOnprogress) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + nsAutoString type; + type.AssignASCII(sListenerTypes[LISTENER_TYPE_PROGRESS]); + + return SetOnXListener(type, aOnprogress); +} + +NS_IMETHODIMP +nsDOMWorkerXHREventTarget::GetOnloadend(nsIDOMEventListener** aOnloadend) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + NS_ENSURE_ARG_POINTER(aOnloadend); + + nsAutoString type; + type.AssignASCII(sListenerTypes[LISTENER_TYPE_LOADEND]); + + nsCOMPtr listener = GetOnXListener(type); + listener.forget(aOnloadend); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHREventTarget::SetOnloadend(nsIDOMEventListener* aOnloadend) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + nsAutoString type; + type.AssignASCII(sListenerTypes[LISTENER_TYPE_LOADEND]); + + return SetOnXListener(type, aOnloadend); +} + +nsDOMWorkerXHRUpload::nsDOMWorkerXHRUpload(nsDOMWorkerXHR* aWorkerXHR) +: mWorkerXHR(aWorkerXHR) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(aWorkerXHR, "Null pointer!"); +} + +NS_IMPL_ISUPPORTS_INHERITED1(nsDOMWorkerXHRUpload, nsDOMWorkerXHREventTarget, + nsIXMLHttpRequestUpload) + +NS_IMPL_CI_INTERFACE_GETTER3(nsDOMWorkerXHRUpload, nsIDOMEventTarget, + nsIXMLHttpRequestEventTarget, + nsIXMLHttpRequestUpload) + +NS_IMPL_THREADSAFE_DOM_CI_GETINTERFACES(nsDOMWorkerXHRUpload) + +NS_IMETHODIMP +nsDOMWorkerXHRUpload::RemoveEventListener(const nsAString& aType, + nsIDOMEventListener* aListener, + PRBool aUseCapture) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + NS_ENSURE_ARG_POINTER(aListener); + + if (mWorkerXHR->mWorker->IsCanceled()) { + return NS_ERROR_ABORT; + } + + return nsDOMWorkerXHREventTarget::RemoveEventListener(aType, aListener, + aUseCapture); +} + +NS_IMETHODIMP +nsDOMWorkerXHRUpload::DispatchEvent(nsIDOMEvent* aEvent, + PRBool* _retval) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + NS_ENSURE_ARG_POINTER(aEvent); + + if (mWorkerXHR->mWorker->IsCanceled()) { + return NS_ERROR_ABORT; + } + + return nsDOMWorkerXHREventTarget::DispatchEvent(aEvent, _retval); +} + +NS_IMETHODIMP +nsDOMWorkerXHRUpload::AddEventListener(const nsAString& aType, + nsIDOMEventListener* aListener, + PRBool aUseCapture, + PRBool aWantsUntrusted, + PRUint8 optional_argc) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + NS_ENSURE_ARG_POINTER(aListener); + + if (mWorkerXHR->mWorker->IsCanceled()) { + return NS_ERROR_ABORT; + } + + nsresult rv = nsDOMWorkerXHREventTarget::AddEventListener(aType, aListener, + aUseCapture, + aWantsUntrusted, + optional_argc); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mWorkerXHR->mXHRProxy->UploadEventListenerAdded(); + if (NS_FAILED(rv)) { + NS_WARNING("UploadEventListenerAdded failed!"); + RemoveEventListener(aType, aListener, aUseCapture); + return rv; + } + + return NS_OK; +} + +nsresult +nsDOMWorkerXHRUpload::SetOnXListener(const nsAString& aType, + nsIDOMEventListener* aListener) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (mWorkerXHR->mCanceled) { + return NS_ERROR_ABORT; + } + + PRUint32 type = GetListenerTypeFromString(aType); + if (type > sMaxUploadEventTypes) { + // Silently ignore junk events. + return NS_OK; + } + + return nsDOMWorkerXHREventTarget::SetOnXListener(aType, aListener); +} + +nsDOMWorkerXHR::nsDOMWorkerXHR(nsDOMWorker* aWorker) +: nsDOMWorkerFeature(aWorker), + mWrappedNative(nsnull), + mCanceled(PR_FALSE) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(aWorker, "Must have a worker!"); +} + +nsDOMWorkerXHR::~nsDOMWorkerXHR() +{ + if (mXHRProxy) { + if (!NS_IsMainThread()) { + nsCOMPtr runnable = + NS_NewRunnableMethod(mXHRProxy, &nsDOMWorkerXHRProxy::Destroy); + + if (runnable) { + mXHRProxy = nsnull; + NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL); + } + } + else { + mXHRProxy->Destroy(); + } + } +} + +// Tricky! We use the AddRef/Release method of nsDOMWorkerFeature (to make sure +// we properly remove ourselves from the worker array) but inherit the QI of +// nsDOMWorkerXHREventTarget. +NS_IMPL_ADDREF_INHERITED(nsDOMWorkerXHR, nsDOMWorkerFeature) +NS_IMPL_RELEASE_INHERITED(nsDOMWorkerXHR, nsDOMWorkerFeature) + +NS_IMPL_QUERY_INTERFACE_INHERITED2(nsDOMWorkerXHR, nsDOMWorkerXHREventTarget, + nsIXMLHttpRequest, + nsIXPCScriptable) + +NS_IMPL_CI_INTERFACE_GETTER3(nsDOMWorkerXHR, nsIDOMEventTarget, + nsIXMLHttpRequestEventTarget, + nsIXMLHttpRequest) + +NS_IMPL_THREADSAFE_DOM_CI_GETINTERFACES(nsDOMWorkerXHR) + +#define XPC_MAP_CLASSNAME nsDOMWorkerXHR +#define XPC_MAP_QUOTED_CLASSNAME "XMLHttpRequest" +#define XPC_MAP_WANT_POSTCREATE +#define XPC_MAP_WANT_TRACE +#define XPC_MAP_WANT_FINALIZE + +#define XPC_MAP_FLAGS \ + nsIXPCScriptable::DONT_ENUM_QUERY_INTERFACE | \ + nsIXPCScriptable::CLASSINFO_INTERFACES_ONLY | \ + nsIXPCScriptable::DONT_REFLECT_INTERFACE_NAMES + +#include "xpc_map_end.h" + +NS_IMETHODIMP +nsDOMWorkerXHR::Trace(nsIXPConnectWrappedNative* /* aWrapper */, + JSTracer* aTracer, + JSObject* /*aObj */) +{ + if (!mCanceled) { + nsDOMWorkerMessageHandler::Trace(aTracer); + if (mUpload) { + mUpload->Trace(aTracer); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHR::Finalize(nsIXPConnectWrappedNative* /* aWrapper */, + JSContext* /* aCx */, + JSObject* /* aObj */) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + nsDOMWorkerMessageHandler::ClearAllListeners(); + + if (mUpload) { + mUpload->ClearAllListeners(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHR::PostCreate(nsIXPConnectWrappedNative* aWrapper, + JSContext* /* aCx */, + JSObject* /* aObj */) +{ + mWrappedNative = aWrapper; + return NS_OK; +} + +nsresult +nsDOMWorkerXHR::Init() +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + nsRefPtr proxy = new nsDOMWorkerXHRProxy(this); + NS_ENSURE_TRUE(proxy, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv = proxy->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + proxy.swap(mXHRProxy); + return NS_OK; +} + +void +nsDOMWorkerXHR::Cancel() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + // Just in case mUpload holds the only ref to this object we make sure to stay + // alive through this call. + nsRefPtr kungFuDeathGrip(this); + + { + // This lock is here to prevent a race between Cancel and GetUpload, not to + // protect mCanceled. + MutexAutoLock lock(mWorker->GetLock()); + + mCanceled = PR_TRUE; + mUpload = nsnull; + } + + if (mXHRProxy) { + mXHRProxy->Destroy(); + mXHRProxy = nsnull; + } + + mWorker = nsnull; +} + +nsresult +nsDOMWorkerXHR::SetOnXListener(const nsAString& aType, + nsIDOMEventListener* aListener) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + PRUint32 type = GetListenerTypeFromString(aType); + if (type > sMaxXHREventTypes) { + // Silently ignore junk events. + return NS_OK; + } + + return nsDOMWorkerXHREventTarget::SetOnXListener(aType, aListener); +} + +NS_IMETHODIMP +nsDOMWorkerXHR::GetChannel(nsIChannel** aChannel) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + NS_ENSURE_ARG_POINTER(aChannel); + *aChannel = nsnull; + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHR::GetResponseXML(nsIDOMDocument** aResponseXML) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + NS_ENSURE_ARG_POINTER(aResponseXML); + *aResponseXML = nsnull; + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHR::GetResponseText(nsAString& aResponseText) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + nsresult rv = mXHRProxy->GetResponseText(aResponseText); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHR::GetStatus(PRUint32* aStatus) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + NS_ENSURE_ARG_POINTER(aStatus); + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + nsresult rv = mXHRProxy->GetStatus(aStatus); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHR::GetStatusText(nsACString& aStatusText) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + nsresult rv = mXHRProxy->GetStatusText(aStatusText); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHR::Abort() +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + nsresult rv = mXHRProxy->Abort(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHR::GetAllResponseHeaders(char** _retval) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + NS_ENSURE_ARG_POINTER(_retval); + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + nsresult rv = mXHRProxy->GetAllResponseHeaders(_retval); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHR::GetResponseHeader(const nsACString& aHeader, + nsACString& _retval) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + nsresult rv = mXHRProxy->GetResponseHeader(aHeader, _retval); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHR::Open(const nsACString& aMethod, const nsACString& aUrl, + PRBool aAsync, const nsAString& aUser, + const nsAString& aPassword, PRUint8 optional_argc) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + if (!optional_argc) { + aAsync = PR_TRUE; + } + + nsresult rv = mXHRProxy->Open(aMethod, aUrl, aAsync, aUser, aPassword); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHR::Send(nsIVariant* aBody) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + if (mWorker->IsClosing() && !mXHRProxy->mSyncRequest) { + // Cheat and don't start this request since we know we'll never be able to + // use the data. + return NS_OK; + } + + nsresult rv = mXHRProxy->Send(aBody); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHR::SendAsBinary(const nsAString& aBody) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + if (mWorker->IsClosing() && !mXHRProxy->mSyncRequest) { + // Cheat and don't start this request since we know we'll never be able to + // use the data. + return NS_OK; + } + + nsresult rv = mXHRProxy->SendAsBinary(aBody); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHR::SetRequestHeader(const nsACString& aHeader, + const nsACString& aValue) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + nsresult rv = mXHRProxy->SetRequestHeader(aHeader, aValue); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHR::GetReadyState(PRUint16* aReadyState) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + NS_ENSURE_ARG_POINTER(aReadyState); + + nsresult rv = mXHRProxy->GetReadyState(aReadyState); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHR::OverrideMimeType(const nsACString& aMimetype) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + nsresult rv = mXHRProxy->OverrideMimeType(aMimetype); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHR::GetMultipart(PRBool* aMultipart) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + NS_ENSURE_ARG_POINTER(aMultipart); + + nsresult rv = mXHRProxy->GetMultipart(aMultipart); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHR::SetMultipart(PRBool aMultipart) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + nsresult rv = mXHRProxy->SetMultipart(aMultipart); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHR::GetMozBackgroundRequest(PRBool* aMozBackgroundRequest) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + NS_ENSURE_ARG_POINTER(aMozBackgroundRequest); + + *aMozBackgroundRequest = PR_FALSE; + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHR::SetMozBackgroundRequest(PRBool aMozBackgroundRequest) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (aMozBackgroundRequest) { + return NS_ERROR_NOT_AVAILABLE; + } + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHR::Init(nsIPrincipal* aPrincipal, + nsIScriptContext* aScriptContext, + nsPIDOMWindow* aOwnerWindow, + nsIURI* aBaseURI) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + NS_NOTREACHED("No one should be calling this!"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDOMWorkerXHR::GetUpload(nsIXMLHttpRequestUpload** aUpload) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + nsRefPtr worker = mWorker; + if (!worker) { + return NS_ERROR_ABORT; + } + + MutexAutoLock lock(worker->GetLock()); + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + NS_ENSURE_ARG_POINTER(aUpload); + + if (!mUpload) { + mUpload = new nsDOMWorkerXHRUpload(this); + NS_ENSURE_TRUE(mUpload, NS_ERROR_OUT_OF_MEMORY); + } + + NS_ADDREF(*aUpload = mUpload); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHR::GetOnreadystatechange(nsIDOMEventListener** aOnreadystatechange) +{ + NS_ENSURE_ARG_POINTER(aOnreadystatechange); + + nsAutoString type; + type.AssignASCII(sListenerTypes[LISTENER_TYPE_READYSTATECHANGE]); + + nsCOMPtr listener = GetOnXListener(type); + listener.forget(aOnreadystatechange); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHR::SetOnreadystatechange(nsIDOMEventListener* aOnreadystatechange) +{ + nsAutoString type; + type.AssignASCII(sListenerTypes[LISTENER_TYPE_READYSTATECHANGE]); + + return SetOnXListener(type, aOnreadystatechange); +} + +NS_IMETHODIMP +nsDOMWorkerXHR::GetWithCredentials(PRBool* aWithCredentials) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + NS_ENSURE_ARG_POINTER(aWithCredentials); + + nsresult rv = mXHRProxy->GetWithCredentials(aWithCredentials); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHR::SetWithCredentials(PRBool aWithCredentials) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + nsresult rv = mXHRProxy->SetWithCredentials(aWithCredentials); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWorkerXHR::GetResponseType(nsAString& aResponseText) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDOMWorkerXHR::SetResponseType(const nsAString& aResponseText) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* readonly attribute jsval response; */ +NS_IMETHODIMP +nsDOMWorkerXHR::GetResponse(JSContext *aCx, jsval *aResult) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/dom/src/threads/nsDOMWorkerXHR.h b/dom/src/threads/nsDOMWorkerXHR.h new file mode 100644 index 000000000000..830dbd2c5d59 --- /dev/null +++ b/dom/src/threads/nsDOMWorkerXHR.h @@ -0,0 +1,172 @@ +/* -*- 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 (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 __NSDOMWORKERXHR_H__ +#define __NSDOMWORKERXHR_H__ + +// Bases +#include "nsIClassInfo.h" +#include "nsIXMLHttpRequest.h" +#include "nsIXPCScriptable.h" + +// Other includes +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" + +// DOMWorker includes +#include "nsDOMWorker.h" +#include "nsDOMWorkerMacros.h" +#include "nsDOMWorkerXHRProxy.h" + +// Convenience defines for event *indexes* in the sListenerTypes array. +#define LISTENER_TYPE_ABORT 0 +#define LISTENER_TYPE_ERROR 1 +#define LISTENER_TYPE_LOAD 2 +#define LISTENER_TYPE_LOADSTART 3 +#define LISTENER_TYPE_PROGRESS 4 +#define LISTENER_TYPE_READYSTATECHANGE 5 +#define LISTENER_TYPE_LOADEND 6 + +class nsIXPConnectWrappedNative; + +class nsDOMWorkerXHREventTarget : public nsDOMWorkerMessageHandler, + public nsIXMLHttpRequestEventTarget +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_FORWARD_NSIDOMEVENTTARGET(nsDOMWorkerMessageHandler::) + NS_DECL_NSIXMLHTTPREQUESTEVENTTARGET + + static const char* const sListenerTypes[]; + static const PRUint32 sMaxXHREventTypes; + static const PRUint32 sMaxUploadEventTypes; + + static PRUint32 GetListenerTypeFromString(const nsAString& aString); + +protected: + virtual ~nsDOMWorkerXHREventTarget() { } +}; + +class nsDOMWorkerXHRUpload; + +class nsDOMWorkerXHR : public nsDOMWorkerFeature, + public nsDOMWorkerXHREventTarget, + public nsIXMLHttpRequest, + public nsIXPCScriptable +{ + typedef mozilla::Mutex Mutex; + + friend class nsDOMWorkerXHREvent; + friend class nsDOMWorkerXHRLastProgressOrLoadEvent; + friend class nsDOMWorkerXHRProxy; + friend class nsDOMWorkerXHRUpload; + +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIXMLHTTPREQUEST + NS_FORWARD_NSICLASSINFO_NOGETINTERFACES(nsDOMWorkerXHREventTarget::) + NS_DECL_NSICLASSINFO_GETINTERFACES + NS_DECL_NSIXPCSCRIPTABLE + + nsDOMWorkerXHR(nsDOMWorker* aWorker); + + nsresult Init(); + + virtual void Cancel(); + + virtual nsresult SetOnXListener(const nsAString& aType, + nsIDOMEventListener* aListener); + +private: + virtual ~nsDOMWorkerXHR(); + + Mutex& GetLock() { + return mWorker->GetLock(); + } + + already_AddRefed GetWrappedNative() { + nsCOMPtr wrappedNative(mWrappedNative); + return wrappedNative.forget(); + } + + nsRefPtr mXHRProxy; + nsRefPtr mUpload; + + nsIXPConnectWrappedNative* mWrappedNative; + + volatile PRBool mCanceled; +}; + +class nsDOMWorkerXHRUpload : public nsDOMWorkerXHREventTarget, + public nsIXMLHttpRequestUpload +{ + friend class nsDOMWorkerXHR; + +public: + NS_DECL_ISUPPORTS_INHERITED + + // nsIDOMEventHandler + NS_FORWARD_INTERNAL_NSIDOMEVENTTARGET(nsDOMWorkerMessageHandler::) + NS_IMETHOD AddEventListener(const nsAString& aType, + nsIDOMEventListener* aListener, + PRBool aUseCapture, + PRBool aWantsUntrusted, + PRUint8 optional_argc); + NS_IMETHOD RemoveEventListener(const nsAString& aType, + nsIDOMEventListener* aListener, + PRBool aUseCapture); + NS_IMETHOD DispatchEvent(nsIDOMEvent* aEvent, + PRBool* _retval); + NS_FORWARD_NSIXMLHTTPREQUESTEVENTTARGET(nsDOMWorkerXHREventTarget::) + NS_DECL_NSIXMLHTTPREQUESTUPLOAD + NS_FORWARD_NSICLASSINFO_NOGETINTERFACES(nsDOMWorkerXHREventTarget::) + NS_DECL_NSICLASSINFO_GETINTERFACES + + nsDOMWorkerXHRUpload(nsDOMWorkerXHR* aWorkerXHR); + + virtual nsresult SetOnXListener(const nsAString& aType, + nsIDOMEventListener* aListener); + +protected: + virtual ~nsDOMWorkerXHRUpload() { } + + nsRefPtr mWorkerXHR; +}; + +#endif /* __NSDOMWORKERXHR_H__ */ diff --git a/dom/src/threads/nsDOMWorkerXHRProxiedFunctions.h b/dom/src/threads/nsDOMWorkerXHRProxiedFunctions.h new file mode 100644 index 000000000000..e12cf38ca716 --- /dev/null +++ b/dom/src/threads/nsDOMWorkerXHRProxiedFunctions.h @@ -0,0 +1,182 @@ +/* -*- 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 (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 __NSDOMWORKERXHRPROXIEDFUNCTIONS_H__ +#define __NSDOMWORKERXHRPROXIEDFUNCTIONS_H__ + +#define MAKE_PROXIED_FUNCTION0(_name) \ + class _name : public SyncEventCapturingRunnable \ + { \ + public: \ + virtual nsresult RunInternal() \ + { \ + nsCOMPtr xhr = mXHR->GetXMLHttpRequest(); \ + if (xhr) { \ + return xhr-> _name (); \ + } \ + return NS_OK; \ + } \ + } + +#define MAKE_PROXIED_FUNCTION1(_name, _arg1) \ + class _name : public SyncEventCapturingRunnable \ + { \ + public: \ + _name (_arg1 aArg1) : mArg1(aArg1) { } \ + \ + virtual nsresult RunInternal() \ + { \ + nsCOMPtr xhr = mXHR->GetXMLHttpRequest(); \ + if (xhr) { \ + return xhr-> _name (mArg1); \ + } \ + return NS_OK; \ + } \ + private: \ + _arg1 mArg1; \ + } + +#define MAKE_PROXIED_FUNCTION2(_name, _arg1, _arg2) \ + class _name : public SyncEventCapturingRunnable \ + { \ + public: \ + _name (_arg1 aArg1, _arg2 aArg2) : mArg1(aArg1), mArg2(aArg2) { } \ + \ + virtual nsresult RunInternal() \ + { \ + nsCOMPtr xhr = mXHR->GetXMLHttpRequest(); \ + if (xhr) { \ + return xhr-> _name (mArg1, mArg2); \ + } \ + return NS_OK; \ + } \ + private: \ + _arg1 mArg1; \ + _arg2 mArg2; \ + } + +namespace nsDOMWorkerProxiedXHRFunctions +{ + typedef nsDOMWorkerXHRProxy::SyncEventQueue SyncEventQueue; + + class SyncEventCapturingRunnable : public nsRunnable + { + public: + SyncEventCapturingRunnable() + : mXHR(nsnull), mQueue(nsnull) { } + + void Init(nsDOMWorkerXHRProxy* aXHR, + SyncEventQueue* aQueue) { + NS_ASSERTION(aXHR, "Null pointer!"); + NS_ASSERTION(aQueue, "Null pointer!"); + + mXHR = aXHR; + mQueue = aQueue; + } + + virtual nsresult RunInternal() = 0; + + NS_IMETHOD Run() { + NS_ASSERTION(mXHR && mQueue, "Forgot to call Init!"); + + SyncEventQueue* oldQueue = mXHR->SetSyncEventQueue(mQueue); + + nsresult rv = RunInternal(); + + mXHR->SetSyncEventQueue(oldQueue); + + return rv; + } + + protected: + nsRefPtr mXHR; + SyncEventQueue* mQueue; + }; + + class Abort : public SyncEventCapturingRunnable + { + public: + virtual nsresult RunInternal() { + return mXHR->Abort(); + } + }; + + class Open : public SyncEventCapturingRunnable + { + public: + Open(const nsACString& aMethod, const nsACString& aUrl, + PRBool aAsync, const nsAString& aUser, + const nsAString& aPassword) + : mMethod(aMethod), mUrl(aUrl), mAsync(aAsync), mUser(aUser), + mPassword(aPassword) { } + + virtual nsresult RunInternal() { + return mXHR->Open(mMethod, mUrl, mAsync, mUser, mPassword); + } + + private: + nsCString mMethod; + nsCString mUrl; + PRBool mAsync; + nsString mUser; + nsString mPassword; + }; + + MAKE_PROXIED_FUNCTION1(GetAllResponseHeaders, char**); + + MAKE_PROXIED_FUNCTION2(GetResponseHeader, const nsACString&, nsACString&); + + MAKE_PROXIED_FUNCTION1(Send, nsIVariant*); + + MAKE_PROXIED_FUNCTION1(SendAsBinary, const nsAString&); + + MAKE_PROXIED_FUNCTION2(SetRequestHeader, const nsACString&, + const nsACString&); + + MAKE_PROXIED_FUNCTION1(OverrideMimeType, const nsACString&); + + MAKE_PROXIED_FUNCTION1(SetMultipart, PRBool); + + MAKE_PROXIED_FUNCTION1(GetMultipart, PRBool*); + + MAKE_PROXIED_FUNCTION1(GetWithCredentials, PRBool*); + + MAKE_PROXIED_FUNCTION1(SetWithCredentials, PRBool); +} + +#endif /* __NSDOMWORKERXHRPROXIEDFUNCTIONS_H__ */ diff --git a/dom/src/threads/nsDOMWorkerXHRProxy.cpp b/dom/src/threads/nsDOMWorkerXHRProxy.cpp new file mode 100644 index 000000000000..87506f563a3c --- /dev/null +++ b/dom/src/threads/nsDOMWorkerXHRProxy.cpp @@ -0,0 +1,1187 @@ +/* -*- 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 (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 "nsDOMWorkerXHRProxy.h" + +// Interfaces +#include "nsIDOMEvent.h" +#include "nsIDOMProgressEvent.h" +#include "nsILoadGroup.h" +#include "nsIRequest.h" +#include "nsIThread.h" +#include "nsIVariant.h" +#include "nsIXMLHttpRequest.h" + +// Other includes +#include "nsComponentManagerUtils.h" +#include "nsIClassInfoImpl.h" +#include "nsThreadUtils.h" +#include "nsXMLHttpRequest.h" +#include "prinrval.h" +#include "prthread.h" + +// DOMWorker includes +#include "nsDOMThreadService.h" +#include "nsDOMWorker.h" +#include "nsDOMWorkerEvents.h" +#include "nsDOMWorkerMacros.h" +#include "nsDOMWorkerPool.h" +#include "nsDOMWorkerXHR.h" +#include "nsDOMWorkerXHRProxiedFunctions.h" + +using namespace mozilla; + +#define MAX_XHR_LISTENER_TYPE nsDOMWorkerXHREventTarget::sMaxXHREventTypes +#define MAX_UPLOAD_LISTENER_TYPE nsDOMWorkerXHREventTarget::sMaxUploadEventTypes + +#define RUN_PROXIED_FUNCTION(_name, _args) \ + PR_BEGIN_MACRO \ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); \ + \ + if (mCanceled) { \ + return NS_ERROR_ABORT; \ + } \ + \ + SyncEventQueue queue; \ + \ + nsRefPtr< :: _name> method = new :: _name _args; \ + NS_ENSURE_TRUE(method, NS_ERROR_OUT_OF_MEMORY); \ + \ + method->Init(this, &queue); \ + \ + nsRefPtr runnable = \ + new nsResultReturningRunnable(mMainThread, method, mWorkerXHR->mWorker); \ + NS_ENSURE_TRUE(runnable, NS_ERROR_OUT_OF_MEMORY); \ + \ + nsresult _rv = runnable->Dispatch(); \ + \ + if (mCanceled) { \ + return NS_ERROR_ABORT; \ + } \ + \ + PRUint32 queueLength = queue.Length(); \ + for (PRUint32 index = 0; index < queueLength; index++) { \ + queue[index]->Run(); \ + } \ + \ + if (NS_FAILED(_rv)) { \ + return _rv; \ + } \ + PR_END_MACRO + +using namespace nsDOMWorkerProxiedXHRFunctions; + +class nsResultReturningRunnable : public nsIRunnable +{ +public: + NS_DECL_ISUPPORTS + + nsResultReturningRunnable(nsIEventTarget* aTarget, nsIRunnable* aRunnable, + nsDOMWorker* aWorker) + : mTarget(aTarget), mRunnable(aRunnable), mWorker(aWorker), + mResult(NS_OK), mDone(PR_FALSE) { } + + nsresult Dispatch() { + if (!mWorker) { + // Must have been canceled, bail out. + return NS_ERROR_ABORT; + } + + nsIThread* currentThread = NS_GetCurrentThread(); + NS_ASSERTION(currentThread, "This should never be null!"); + + nsresult rv = mTarget->Dispatch(this, NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + + // Complicated logic: Spin events while we haven't been canceled (if a + // cancel boolean was supplied) and while we're not done. + while (!mWorker->IsCanceled() && !mDone) { + // Process a single event or yield if no event is pending. + if (!NS_ProcessNextEvent(currentThread, PR_FALSE)) { + PR_Sleep(PR_INTERVAL_NO_WAIT); + } + } + + if (mWorker->IsCanceled()) { + mResult = NS_ERROR_ABORT; + } + + return mResult; + } + + NS_IMETHOD Run() { +#ifdef DEBUG + PRBool rightThread = PR_FALSE; + mTarget->IsOnCurrentThread(&rightThread); + NS_ASSERTION(rightThread, "Run called on the wrong thread!"); +#endif + + mResult = mWorker->IsCanceled() ? NS_ERROR_ABORT : mRunnable->Run(); + mDone = PR_TRUE; + + return mResult; + } + +private: + nsCOMPtr mTarget; + nsCOMPtr mRunnable; + nsRefPtr mWorker; + nsresult mResult; + volatile PRBool mDone; +}; + +NS_IMPL_THREADSAFE_ISUPPORTS1(nsResultReturningRunnable, nsIRunnable) + +class nsDOMWorkerXHRLastProgressOrLoadEvent : public nsIRunnable +{ +public: + NS_DECL_ISUPPORTS + + nsDOMWorkerXHRLastProgressOrLoadEvent(nsDOMWorkerXHRProxy* aProxy) + : mProxy(aProxy) { + NS_ASSERTION(aProxy, "Null pointer!"); + } + + NS_IMETHOD Run() { + nsRefPtr lastProgressOrLoadEvent; + + if (!mProxy->mCanceled) { + MutexAutoLock lock(mProxy->mWorkerXHR->GetLock()); + mProxy->mLastProgressOrLoadEvent.swap(lastProgressOrLoadEvent); + if (mProxy->mCanceled) { + return NS_ERROR_ABORT; + } + } + + if (lastProgressOrLoadEvent) { + return lastProgressOrLoadEvent->Run(); + } + + return NS_OK; + } + +private: + nsRefPtr mProxy; +}; + +NS_IMPL_THREADSAFE_ISUPPORTS1(nsDOMWorkerXHRLastProgressOrLoadEvent, + nsIRunnable) + +class nsDOMWorkerXHRWrappedListener : public nsIDOMEventListener +{ +public: + NS_DECL_ISUPPORTS + + nsDOMWorkerXHRWrappedListener(nsIDOMEventListener* aInner) + : mInner(aInner) { + NS_ASSERTION(aInner, "Null pointer!"); + } + + NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) { + return mInner->HandleEvent(aEvent); + } + + nsIDOMEventListener* Inner() { + return mInner; + } + +private: + nsCOMPtr mInner; +}; + +NS_IMPL_THREADSAFE_ISUPPORTS1(nsDOMWorkerXHRWrappedListener, + nsIDOMEventListener) + +class nsDOMWorkerXHRAttachUploadListenersRunnable : public nsIRunnable +{ +public: + NS_DECL_ISUPPORTS + + nsDOMWorkerXHRAttachUploadListenersRunnable(nsDOMWorkerXHRProxy* aProxy) + : mProxy(aProxy) { + NS_ASSERTION(aProxy, "Null pointer!"); + } + + NS_IMETHOD Run() { + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + NS_ASSERTION(!mProxy->mWantUploadListeners, "Huh?!"); + + nsCOMPtr upload(do_QueryInterface(mProxy->mUpload)); + NS_ASSERTION(upload, "This shouldn't fail!"); + + nsAutoString eventName; + for (PRUint32 index = 0; index < MAX_UPLOAD_LISTENER_TYPE; index++) { + eventName.AssignASCII(nsDOMWorkerXHREventTarget::sListenerTypes[index]); + upload->AddEventListener(eventName, mProxy, PR_FALSE); + } + + mProxy->mWantUploadListeners = PR_TRUE; + return NS_OK; + } + +private: + nsRefPtr mProxy; +}; + +NS_IMPL_THREADSAFE_ISUPPORTS1(nsDOMWorkerXHRAttachUploadListenersRunnable, + nsIRunnable) + +class nsDOMWorkerXHRFinishSyncXHRRunnable : public nsIRunnable +{ +public: + NS_DECL_ISUPPORTS + + nsDOMWorkerXHRFinishSyncXHRRunnable(nsDOMWorkerXHRProxy* aProxy, + nsIThread* aTarget) + : mProxy(aProxy), mTarget(aTarget) { + NS_ASSERTION(aProxy, "Null pointer!"); + NS_ASSERTION(aTarget, "Null pointer!"); + } + + NS_IMETHOD Run() { + nsCOMPtr thread; + mProxy->mSyncXHRThread.swap(thread); + mProxy = nsnull; + + NS_ASSERTION(thread, "Should have a thread here!"); + NS_ASSERTION(!NS_IsMainThread() && thread == NS_GetCurrentThread(), + "Wrong thread?!"); + + return NS_ProcessPendingEvents(thread); + } + + nsresult Dispatch() { + nsresult rv = mTarget->Dispatch(this, NS_DISPATCH_NORMAL); + mTarget = nsnull; + return rv; + } + +private: + nsRefPtr mProxy; + nsCOMPtr mTarget; +}; + +NS_IMPL_THREADSAFE_ISUPPORTS1(nsDOMWorkerXHRFinishSyncXHRRunnable, nsIRunnable) + +nsDOMWorkerXHRProxy::nsDOMWorkerXHRProxy(nsDOMWorkerXHR* aWorkerXHR) +: mWorkerXHR(aWorkerXHR), + mXHR(nsnull), + mConcreteXHR(nsnull), + mUpload(nsnull), + mSyncEventQueue(nsnull), + mChannelID(-1), + mOwnedByXHR(PR_FALSE), + mWantUploadListeners(PR_FALSE), + mCanceled(PR_FALSE), + mSyncRequest(PR_FALSE) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(MAX_XHR_LISTENER_TYPE >= MAX_UPLOAD_LISTENER_TYPE, + "Upload should support a subset of XHR's event types!"); +} + +nsDOMWorkerXHRProxy::~nsDOMWorkerXHRProxy() +{ + if (mOwnedByXHR) { + mWorkerXHRWN = nsnull; + } + NS_ASSERTION(!mXHR, "Destroy not called!"); +} + +NS_IMPL_THREADSAFE_ISUPPORTS3(nsDOMWorkerXHRProxy, nsIRunnable, + nsIDOMEventListener, + nsIRequestObserver) + +nsresult +nsDOMWorkerXHRProxy::Init() +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + NS_ENSURE_FALSE(mXHR, NS_ERROR_ALREADY_INITIALIZED); + + mMainThread = do_GetMainThread(); + NS_ENSURE_TRUE(mMainThread, NS_ERROR_UNEXPECTED); + + nsRefPtr runnable = + new nsResultReturningRunnable(mMainThread, this, mWorkerXHR->mWorker); + NS_ENSURE_TRUE(runnable, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv = runnable->Dispatch(); + if (NS_FAILED(rv)) { + // Only warn if we didn't get canceled. + NS_WARN_IF_FALSE(rv == NS_ERROR_ABORT, "Dispatch failed!"); + + if (mXHR) { + // Need to call DestroyInternal on the main thread! +#ifdef DEBUG + nsresult rvDebug = +#endif + mMainThread->Dispatch(this, NS_DISPATCH_NORMAL); + NS_ASSERTION(NS_SUCCEEDED(rvDebug), "Going to leak!"); + } + + return rv; + } + + return NS_OK; +} + +nsIXMLHttpRequest* +nsDOMWorkerXHRProxy::GetXMLHttpRequest() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + return mCanceled ? nsnull : mXHR; +} + +nsresult +nsDOMWorkerXHRProxy::Destroy() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + { + MutexAutoLock lock(mWorkerXHR->GetLock()); + + mCanceled = PR_TRUE; + + mLastProgressOrLoadEvent = nsnull; + mLastXHRState = nsnull; + } + + DestroyInternal(); + + NS_ASSERTION(!(mLastProgressOrLoadEvent || mLastXHRState), "Going to leak!"); + + return NS_OK; +} + +nsresult +nsDOMWorkerXHRProxy::InitInternal() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(!mXHR, "InitInternal shouldn't be called twice!"); + + nsDOMWorker* worker = mWorkerXHR->mWorker; + if (worker->IsCanceled()) { + return NS_ERROR_ABORT; + } + + NS_ASSERTION(worker->GetPrincipal(), "Must have a principal!"); + NS_ASSERTION(worker->GetBaseURI(), "Must have a URI!"); + + nsIScriptGlobalObject* sgo = worker->Pool()->ScriptGlobalObject(); + nsIScriptContext* scriptContext = sgo ? sgo->GetContext() : nsnull; + + nsCOMPtr ownerWindow = do_QueryInterface(sgo); + + nsRefPtr xhrConcrete = new nsXMLHttpRequest(); + NS_ENSURE_TRUE(xhrConcrete, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv = xhrConcrete->Init(worker->GetPrincipal(), scriptContext, + ownerWindow, worker->GetBaseURI()); + NS_ENSURE_SUCCESS(rv, rv); + + // Call QI manually here to avoid keeping up with the cast madness of + // nsXMLHttpRequest. + nsCOMPtr xhr = + do_QueryInterface(static_cast(xhrConcrete)); + NS_ENSURE_TRUE(xhr, NS_ERROR_NO_INTERFACE); + + nsCOMPtr upload; + rv = xhr->GetUpload(getter_AddRefs(upload)); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr nullState = new nsDOMWorkerXHRState(); + NS_ENSURE_TRUE(nullState, NS_ERROR_OUT_OF_MEMORY); + + nsDOMWorkerXHREvent::SnapshotXHRState(xhr, nullState); + mLastXHRState.swap(nullState); + + xhrConcrete->SetRequestObserver(this); + + // We now own mXHR and it owns upload. + xhr.swap(mXHR); + + // Weak refs. + mUpload = upload; + mConcreteXHR = xhrConcrete; + + AddRemoveXHRListeners(PR_TRUE); + + return NS_OK; +} + +void +nsDOMWorkerXHRProxy::DestroyInternal() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + nsRefPtr kungFuDeathGrip(this); + + if (mConcreteXHR) { + mConcreteXHR->SetRequestObserver(nsnull); + } + + if (mOwnedByXHR) { + mXHR->Abort(); + } + else { + // There's a slight chance that Send was called yet we've canceled before + // necko has fired its OnStartRequest notification. Guard against that here. + nsRefPtr syncFinishedRunnable; + { + MutexAutoLock lock(mWorkerXHR->GetLock()); + mSyncFinishedRunnable.swap(syncFinishedRunnable); + } + + if (syncFinishedRunnable) { + syncFinishedRunnable->Dispatch(); + } + } + + NS_ASSERTION(!mOwnedByXHR, "Should have flipped already!"); + NS_ASSERTION(!mSyncFinishedRunnable, "Should have fired this already!"); + + // mXHR could be null if Init fails. + if (mXHR) { + AddRemoveXHRListeners(PR_FALSE); + + mXHR->Release(); + mXHR = nsnull; + + mUpload = nsnull; + } +} + +void +nsDOMWorkerXHRProxy::AddRemoveXHRListeners(PRBool aAdd) +{ + nsCOMPtr xhrTarget(do_QueryInterface(mXHR)); + NS_ASSERTION(xhrTarget, "This shouldn't fail!"); + + nsAutoString eventName; + PRUint32 index = 0; + + if (mWantUploadListeners) { + nsCOMPtr uploadTarget(do_QueryInterface(mUpload)); + NS_ASSERTION(uploadTarget, "This shouldn't fail!"); + + for (; index < MAX_UPLOAD_LISTENER_TYPE; index++) { + eventName.AssignASCII(nsDOMWorkerXHREventTarget::sListenerTypes[index]); + if (aAdd) { + xhrTarget->AddEventListener(eventName, this, PR_FALSE); + uploadTarget->AddEventListener(eventName, this, PR_FALSE); + } + else { + xhrTarget->RemoveEventListener(eventName, this, PR_FALSE); + uploadTarget->RemoveEventListener(eventName, this, PR_FALSE); + } + } + } + + for (; index < MAX_XHR_LISTENER_TYPE; index++) { + eventName.AssignASCII(nsDOMWorkerXHREventTarget::sListenerTypes[index]); + if (aAdd) { + xhrTarget->AddEventListener(eventName, this, PR_FALSE); + } + else { + xhrTarget->RemoveEventListener(eventName, this, PR_FALSE); + } + } +} + +void +nsDOMWorkerXHRProxy::FlipOwnership() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + // Flip! + mOwnedByXHR = !mOwnedByXHR; + + // If mWorkerXHR has no outstanding refs from JS then we are about to die. + // Hold an extra ref here to make sure that we live through this call. + nsRefPtr kungFuDeathGrip(this); + + if (mOwnedByXHR) { + mWorkerXHRWN = mWorkerXHR->GetWrappedNative(); + NS_ASSERTION(mWorkerXHRWN, "Null pointer!"); + mXHR->Release(); + } + else { + mXHR->AddRef(); + mWorkerXHRWN = nsnull; + } +} + +nsresult +nsDOMWorkerXHRProxy::UploadEventListenerAdded() +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + // If this is the first time we're setting an upload listener then we have to + // hit the main thread to attach the upload listeners. Otherwise there's + // nothing to do here. + if (mWantUploadListeners) { + return NS_OK; + } + + nsRefPtr attachRunnable = + new nsDOMWorkerXHRAttachUploadListenersRunnable(this); + NS_ENSURE_TRUE(attachRunnable, NS_ERROR_OUT_OF_MEMORY); + + nsRefPtr runnable = + new nsResultReturningRunnable(mMainThread, attachRunnable, + mWorkerXHR->mWorker); + NS_ENSURE_TRUE(runnable, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv = runnable->Dispatch(); + if (NS_FAILED(rv)) { + return rv; + } + + NS_ASSERTION(mWantUploadListeners, "Should have set this!"); + return NS_OK; +} + +nsresult +nsDOMWorkerXHRProxy::HandleWorkerEvent(nsDOMWorkerXHREvent* aEvent, + PRBool aUploadEvent) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(aEvent, "Should not be null!"); + + { + MutexAutoLock lock(mWorkerXHR->GetLock()); + + if (mCanceled || + (aEvent->mChannelID != -1 && aEvent->mChannelID != mChannelID)) { + return NS_OK; + } + + mLastXHRState = aEvent->ForgetState(); + } + +#ifdef DEBUG + if (aUploadEvent) { + NS_ASSERTION(mWorkerXHR->mUpload, "No upload object!"); + } +#endif + + PRUint32& type = aEvent->mXHREventType; + if (type == LISTENER_TYPE_ABORT || type == LISTENER_TYPE_ERROR || + type == LISTENER_TYPE_LOAD) { + nsAutoPtr& progressInfo = aUploadEvent ? + mUploadProgressInfo : + mDownloadProgressInfo; + progressInfo = nsnull; + + // Dummy memory barrier. + MutexAutoLock lock(mWorkerXHR->GetLock()); + } + + nsIDOMEventTarget* target = aUploadEvent ? + static_cast(mWorkerXHR->mUpload) : + static_cast(mWorkerXHR); + + return target->DispatchEvent(static_cast(aEvent), nsnull); +} + +PRBool +nsDOMWorkerXHRProxy::IsUploadEvent(nsIDOMEvent* aEvent) +{ + NS_ASSERTION(aEvent, "Null pointer!"); + + nsCOMPtr target; + if (NS_SUCCEEDED(aEvent->GetTarget(getter_AddRefs(target)))) { + nsCOMPtr upload(do_QueryInterface(target)); + if (upload) { + return PR_TRUE; + } + } + + return PR_FALSE; +} + +nsresult +nsDOMWorkerXHRProxy::DispatchPrematureAbortEvents(PRUint32 aType, + nsIDOMEventTarget* aTarget, + ProgressInfo* aProgressInfo) +{ + nsAutoString type; + type.AssignASCII(nsDOMWorkerXHREventTarget::sListenerTypes[aType]); + + nsresult rv; + + nsRefPtr event; + if (aProgressInfo) { + nsRefPtr progressEvent = + new nsDOMWorkerProgressEvent(); + NS_ENSURE_TRUE(progressEvent, NS_ERROR_OUT_OF_MEMORY); + + rv = progressEvent->InitProgressEvent(type, PR_FALSE, PR_FALSE, + aProgressInfo->computable, + aProgressInfo->loaded, + aProgressInfo->total); + NS_ENSURE_SUCCESS(rv, rv); + + event = progressEvent; + } + else { + event = new nsDOMWorkerEvent(); + NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY); + + rv = event->InitEvent(type, PR_FALSE, PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + } + + event->SetTarget(aTarget); + + nsRefPtr xhrEvent = new nsDOMWorkerXHREvent(this); + NS_ENSURE_TRUE(xhrEvent, NS_ERROR_OUT_OF_MEMORY); + + rv = xhrEvent->Init(aType, type, event, nsDOMWorkerXHREvent::NO_SNAPSHOT); + NS_ENSURE_SUCCESS(rv, rv); + + nsDOMWorkerXHRState* state = xhrEvent->GetState(); + NS_ASSERTION(state, "Should never be null if Init succeeded!"); + + NS_ASSERTION(state->responseText.IsEmpty(), "Should be empty!"); + state->readyState = 4; // COMPLETED, from nsXMLHttpRequest + mXHR->GetStatusText(state->statusText); + mXHR->GetStatus(&state->status); + + return HandleEventRunnable(xhrEvent); +} + +nsresult +nsDOMWorkerXHRProxy::MaybeDispatchPrematureAbortEvents(PRBool aFromOpen) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + nsresult rv; + nsIDOMEventTarget* target; + + if (mDownloadProgressInfo) { + target = static_cast(mWorkerXHR); + NS_ASSERTION(target, "Must have target here!"); + + rv = DispatchPrematureAbortEvents(LISTENER_TYPE_READYSTATECHANGE, target, + nsnull); + NS_ENSURE_SUCCESS(rv, rv); + + if (aFromOpen) { + rv = DispatchPrematureAbortEvents(LISTENER_TYPE_ABORT, target, + mDownloadProgressInfo); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + if (mUploadProgressInfo) { + target = static_cast(mWorkerXHR->mUpload); + NS_ASSERTION(target, "Must have upload here!"); + + rv = DispatchPrematureAbortEvents(LISTENER_TYPE_ABORT, target, + mUploadProgressInfo); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +// nsIDOMEventListener +NS_IMETHODIMP +nsDOMWorkerXHRProxy::HandleEvent(nsIDOMEvent* aEvent) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + NS_ENSURE_ARG_POINTER(aEvent); + + nsAutoString typeString; + nsresult rv = aEvent->GetType(typeString); + NS_ENSURE_SUCCESS(rv, rv); + + PRUint32 type = + nsDOMWorkerXHREventTarget::GetListenerTypeFromString(typeString); + + PRBool isUpload = IsUploadEvent(aEvent); + + if ((isUpload && type >= MAX_UPLOAD_LISTENER_TYPE) || + (!isUpload && type >= MAX_XHR_LISTENER_TYPE)) { + NS_ERROR("We shouldn't ever get a strange event from main thread XHR!"); + return NS_OK; + } + + // This will be filled if the request has completed and we're running in + // sync mode. + nsRefPtr syncFinishedRunnable; + + PRBool requestDone; + if (type == LISTENER_TYPE_ABORT || type == LISTENER_TYPE_ERROR || + type == LISTENER_TYPE_LOAD) { + requestDone = PR_TRUE; + + nsAutoPtr& progressInfo = isUpload ? + mUploadProgressInfo : + mDownloadProgressInfo; + if (!progressInfo) { + progressInfo = new ProgressInfo(); + NS_WARN_IF_FALSE(progressInfo, "Out of memory!"); + } + + if (progressInfo) { + nsCOMPtr progressEvent(do_QueryInterface(aEvent)); + NS_ASSERTION(progressEvent, "Should always QI to nsIDOMProgressEvent!"); + if (progressEvent) { + progressEvent->GetLengthComputable(&progressInfo->computable); + progressEvent->GetLoaded(&progressInfo->loaded); + progressEvent->GetTotal(&progressInfo->total); + } + } + + NS_ASSERTION(!syncFinishedRunnable, "This shouldn't be set!"); + + MutexAutoLock lock(mWorkerXHR->GetLock()); + mSyncFinishedRunnable.swap(syncFinishedRunnable); + } + else { + requestDone = PR_FALSE; + } + + if (mCanceled) { + // When Abort is called on nsXMLHttpRequest (either from a proxied Abort + // call or from DestroyInternal) the OnStopRequest call is not run + // synchronously. Thankfully an abort event *is* fired synchronously so we + // can flip our ownership around and fire the sync finished runnable if + // we're running in sync mode. + if (type == LISTENER_TYPE_ABORT) { + OnStopRequest(nsnull, nsnull, NS_ERROR_ABORT); + } + + // Always bail out if we're canceled. + return NS_ERROR_ABORT; + } + + // Bail out now if this event is 1) not a final event and 2) there are no + // listeners for it. + if (!requestDone && + !mWorkerXHR->HasListeners(typeString) && + !(isUpload && mWorkerXHR->mUpload->HasListeners(typeString))) { + return NS_OK; + } + + nsRefPtr newEvent = new nsDOMWorkerXHREvent(this); + NS_ENSURE_TRUE(newEvent, NS_ERROR_OUT_OF_MEMORY); + + rv = newEvent->Init(type, typeString, aEvent); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr runnable(newEvent); + + if (type == LISTENER_TYPE_LOAD || type == LISTENER_TYPE_PROGRESS) { + runnable = new nsDOMWorkerXHRLastProgressOrLoadEvent(this); + NS_ENSURE_TRUE(runnable, NS_ERROR_OUT_OF_MEMORY); + + { + MutexAutoLock lock(mWorkerXHR->GetLock()); + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + mLastProgressOrLoadEvent.swap(newEvent); + + if (newEvent) { + // Already had a saved progress/load event so no need to generate + // another. Bail out rather than dispatching runnable. + return NS_OK; + } + } + } + + rv = HandleEventRunnable(runnable); + + if (syncFinishedRunnable) { + syncFinishedRunnable->Dispatch(); + } + + return rv; +} + +nsresult +nsDOMWorkerXHRProxy::HandleEventRunnable(nsIRunnable* aRunnable) +{ + NS_ASSERTION(aRunnable, "Null pointer!"); + + nsresult rv; + + if (mSyncEventQueue) { + // If we're supposed to be capturing events for synchronous execution then + // place this event in the queue. + nsCOMPtr* newElement = + mSyncEventQueue->AppendElement(aRunnable); + NS_ENSURE_TRUE(newElement, NS_ERROR_OUT_OF_MEMORY); + } + else if (mSyncXHRThread) { + // If we're running a sync XHR then schedule the event immediately for the + // worker's thread. + rv = mSyncXHRThread->Dispatch(aRunnable, NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + } + else { + // Otherwise schedule it for the worker via the thread service. + rv = nsDOMThreadService::get()->Dispatch(mWorkerXHR->mWorker, aRunnable); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +nsresult +nsDOMWorkerXHRProxy::Open(const nsACString& aMethod, + const nsACString& aUrl, + PRBool aAsync, + const nsAString& aUser, + const nsAString& aPassword) +{ + if (!NS_IsMainThread()) { + mSyncRequest = !aAsync; + + // Always do async behind the scenes! + RUN_PROXIED_FUNCTION(Open, + (aMethod, aUrl, PR_TRUE, aUser, aPassword)); + return NS_OK; + } + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + nsresult rv = MaybeDispatchPrematureAbortEvents(PR_TRUE); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mXHR->Open(aMethod, aUrl, aAsync, aUser, aPassword); + NS_ENSURE_SUCCESS(rv, rv); + + // Do this after Open is called so that we will continue to run events + // from the old channel if Open fails. Any events generated by the + // Open method will always run regardless of channel ID. + mChannelID++; + + return NS_OK; +} + +nsresult +nsDOMWorkerXHRProxy::Abort() +{ + if (!NS_IsMainThread()) { + RUN_PROXIED_FUNCTION(Abort, ()); + return NS_OK; + } + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + nsresult rv = MaybeDispatchPrematureAbortEvents(PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mXHR->Abort(); + NS_ENSURE_SUCCESS(rv, rv); + + // Don't allow further events from this channel. + mChannelID++; + + return NS_OK; +} + +nsDOMWorkerXHRProxy::SyncEventQueue* +nsDOMWorkerXHRProxy::SetSyncEventQueue(SyncEventQueue* aQueue) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + SyncEventQueue* oldQueue = mSyncEventQueue; + mSyncEventQueue = aQueue; + return oldQueue; +} + +nsresult +nsDOMWorkerXHRProxy::GetAllResponseHeaders(char** _retval) +{ + RUN_PROXIED_FUNCTION(GetAllResponseHeaders, (_retval)); + return NS_OK; +} + +nsresult +nsDOMWorkerXHRProxy::GetResponseHeader(const nsACString& aHeader, + nsACString& _retval) +{ + RUN_PROXIED_FUNCTION(GetResponseHeader, (aHeader, _retval)); + return NS_OK; +} + +nsresult +nsDOMWorkerXHRProxy::Send(nsIVariant* aBody) +{ + NS_ASSERTION(!mSyncXHRThread, "Shouldn't reenter here!"); + + if (mSyncRequest) { + + mSyncXHRThread = NS_GetCurrentThread(); + NS_ENSURE_TRUE(mSyncXHRThread, NS_ERROR_FAILURE); + + MutexAutoLock lock(mWorkerXHR->GetLock()); + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + mSyncFinishedRunnable = + new nsDOMWorkerXHRFinishSyncXHRRunnable(this, mSyncXHRThread); + NS_ENSURE_TRUE(mSyncFinishedRunnable, NS_ERROR_FAILURE); + } + + RUN_PROXIED_FUNCTION(Send, (aBody)); + + return RunSyncEventLoop(); +} + +nsresult +nsDOMWorkerXHRProxy::SendAsBinary(const nsAString& aBody) +{ + NS_ASSERTION(!mSyncXHRThread, "Shouldn't reenter here!"); + + if (mSyncRequest) { + mSyncXHRThread = NS_GetCurrentThread(); + NS_ENSURE_TRUE(mSyncXHRThread, NS_ERROR_FAILURE); + + MutexAutoLock lock(mWorkerXHR->GetLock()); + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + mSyncFinishedRunnable = + new nsDOMWorkerXHRFinishSyncXHRRunnable(this, mSyncXHRThread); + NS_ENSURE_TRUE(mSyncFinishedRunnable, NS_ERROR_FAILURE); + } + + RUN_PROXIED_FUNCTION(SendAsBinary, (aBody)); + + return RunSyncEventLoop(); +} + +nsresult +nsDOMWorkerXHRProxy::SetRequestHeader(const nsACString& aHeader, + const nsACString& aValue) +{ + RUN_PROXIED_FUNCTION(SetRequestHeader, (aHeader, aValue)); + return NS_OK; +} + +nsresult +nsDOMWorkerXHRProxy::OverrideMimeType(const nsACString& aMimetype) +{ + RUN_PROXIED_FUNCTION(OverrideMimeType, (aMimetype)); + return NS_OK; +} + +nsresult +nsDOMWorkerXHRProxy::GetMultipart(PRBool* aMultipart) +{ + NS_ASSERTION(aMultipart, "Null pointer!"); + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + RUN_PROXIED_FUNCTION(GetMultipart, (aMultipart)); + return NS_OK; +} + +nsresult +nsDOMWorkerXHRProxy::SetMultipart(PRBool aMultipart) +{ + RUN_PROXIED_FUNCTION(SetMultipart, (aMultipart)); + return NS_OK; +} + +nsresult +nsDOMWorkerXHRProxy::GetWithCredentials(PRBool* aWithCredentials) +{ + RUN_PROXIED_FUNCTION(GetWithCredentials, (aWithCredentials)); + return NS_OK; +} + +nsresult +nsDOMWorkerXHRProxy::SetWithCredentials(PRBool aWithCredentials) +{ + RUN_PROXIED_FUNCTION(SetWithCredentials, (aWithCredentials)); + return NS_OK; +} + +nsresult +nsDOMWorkerXHRProxy::GetResponseText(nsAString& _retval) +{ + if (mCanceled) { + return NS_ERROR_ABORT; + } + + if (NS_SUCCEEDED(mLastXHRState->responseTextResult)) { + _retval.Assign(mLastXHRState->responseText); + } + return mLastXHRState->responseTextResult; +} + +nsresult +nsDOMWorkerXHRProxy::GetStatusText(nsACString& _retval) +{ + if (mCanceled) { + return NS_ERROR_ABORT; + } + + if (NS_SUCCEEDED(mLastXHRState->statusTextResult)) { + _retval.Assign(mLastXHRState->statusText); + } + return mLastXHRState->statusTextResult; +} + +nsresult +nsDOMWorkerXHRProxy::GetStatus(nsresult* _retval) +{ + NS_ASSERTION(_retval, "Null pointer!"); + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + if (NS_SUCCEEDED(mLastXHRState->statusResult)) { + *_retval = mLastXHRState->status; + } + return mLastXHRState->statusResult; +} + +nsresult +nsDOMWorkerXHRProxy::GetReadyState(PRUint16* _retval) +{ + NS_ASSERTION(_retval, "Null pointer!"); + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + if (NS_SUCCEEDED(mLastXHRState->readyStateResult)) { + *_retval = mLastXHRState->readyState; + } + return mLastXHRState->readyStateResult; +} + +nsresult +nsDOMWorkerXHRProxy::RunSyncEventLoop() +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (!mSyncRequest) { + return NS_OK; + } + + NS_ASSERTION(mSyncXHRThread == NS_GetCurrentThread(), "Wrong thread!"); + + while (mSyncXHRThread) { + if (NS_UNLIKELY(!NS_ProcessNextEvent(mSyncXHRThread))) { + NS_ERROR("Something wrong here, this shouldn't fail!"); + return NS_ERROR_UNEXPECTED; + } + } + + NS_ASSERTION(!NS_HasPendingEvents(NS_GetCurrentThread()), + "Unprocessed events remaining!"); + + return NS_OK; +} + +// nsIRunnable +NS_IMETHODIMP +nsDOMWorkerXHRProxy::Run() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + nsresult rv = NS_OK; + + if (!mXHR) { + rv = InitInternal(); + if (NS_SUCCEEDED(rv)) { + return NS_OK; + } + NS_WARNING("InitInternal failed!"); + } + + DestroyInternal(); + return rv; +} + +// nsIRequestObserver +NS_IMETHODIMP +nsDOMWorkerXHRProxy::OnStartRequest(nsIRequest* /* aRequest */, + nsISupports* /* aContext */) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + NS_ASSERTION(!mOwnedByXHR, "Inconsistent state!"); + + if (mCanceled) { + return NS_OK; + } + + FlipOwnership(); + + return NS_OK; +} + +// nsIRequestObserver +NS_IMETHODIMP +nsDOMWorkerXHRProxy::OnStopRequest(nsIRequest* /* aRequest */, + nsISupports* /* aContext */, + nsresult /* aStatus */) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + NS_ASSERTION(mOwnedByXHR, "Inconsistent state!"); + + FlipOwnership(); + + return NS_OK; +} diff --git a/dom/src/threads/nsDOMWorkerXHRProxy.h b/dom/src/threads/nsDOMWorkerXHRProxy.h new file mode 100644 index 000000000000..873826fd03ec --- /dev/null +++ b/dom/src/threads/nsDOMWorkerXHRProxy.h @@ -0,0 +1,198 @@ +/* -*- 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 (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 __NSDOMWORKERXHRPROXY_H__ +#define __NSDOMWORKERXHRPROXY_H__ + +// Bases +#include "nsThreadUtils.h" +#include "nsIDOMEventListener.h" +#include "nsIRequestObserver.h" + +// Other includes +#include "nsIDOMEventTarget.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsStringGlue.h" +#include "nsTArray.h" + +class nsIJSXMLHttpRequest; +class nsIThread; +class nsIVariant; +class nsIXMLHttpRequest; +class nsIXMLHttpRequestUpload; +class nsIXPConnectWrappedNative; +class nsDOMWorkerXHR; +class nsDOMWorkerXHREvent; +class nsDOMWorkerXHRFinishSyncXHRRunnable; +class nsDOMWorkerXHRState; +class nsDOMWorkerXHRWrappedListener; +class nsXMLHttpRequest; + +class nsDOMWorkerXHRProxy : public nsIRunnable, + public nsIDOMEventListener, + public nsIRequestObserver +{ + friend class nsDOMWorkerXHRAttachUploadListenersRunnable; + friend class nsDOMWorkerXHREvent; + friend class nsDOMWorkerXHRFinishSyncXHRRunnable; + friend class nsDOMWorkerXHRLastProgressOrLoadEvent; + friend class nsDOMWorkerXHR; + friend class nsDOMWorkerXHRUpload; + +public: + typedef nsAutoTArray, 5> SyncEventQueue; + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMEVENTLISTENER + NS_DECL_NSIRUNNABLE + NS_DECL_NSIREQUESTOBSERVER + + nsDOMWorkerXHRProxy(nsDOMWorkerXHR* aWorkerXHR); + virtual ~nsDOMWorkerXHRProxy(); + + nsresult Init(); + + nsIXMLHttpRequest* GetXMLHttpRequest(); + + nsresult Open(const nsACString& aMethod, + const nsACString& aUrl, + PRBool aAsync, + const nsAString& aUser, + const nsAString& aPassword); + + nsresult Abort(); + + SyncEventQueue* SetSyncEventQueue(SyncEventQueue* aQueue); + + PRInt32 ChannelID() { + return mChannelID; + } + +protected: + struct ProgressInfo { + ProgressInfo() : computable(PR_FALSE), loaded(0), total(0) { } + + PRBool computable; + PRUint64 loaded; + PRUint64 total; + }; + + nsresult InitInternal(); + void DestroyInternal(); + + nsresult Destroy(); + + void AddRemoveXHRListeners(PRBool aAdd); + void FlipOwnership(); + + nsresult UploadEventListenerAdded(); + + nsresult HandleWorkerEvent(nsDOMWorkerXHREvent* aEvent, + PRBool aUploadEvent); + + nsresult HandleEventRunnable(nsIRunnable* aRunnable); + + // Methods of nsIXMLHttpRequest that we implement + nsresult GetAllResponseHeaders(char** _retval); + nsresult GetResponseHeader(const nsACString& aHeader, + nsACString& _retval); + nsresult Send(nsIVariant* aBody); + nsresult SendAsBinary(const nsAString& aBody); + nsresult GetResponseText(nsAString& _retval); + nsresult GetStatusText(nsACString& _retval); + nsresult GetStatus(nsresult* _retval); + nsresult GetReadyState(PRUint16* _retval); + nsresult SetRequestHeader(const nsACString& aHeader, + const nsACString& aValue); + nsresult OverrideMimeType(const nsACString& aMimetype); + nsresult GetMultipart(PRBool* aMultipart); + nsresult SetMultipart(PRBool aMultipart); + nsresult GetWithCredentials(PRBool* aWithCredentials); + nsresult SetWithCredentials(PRBool aWithCredentials); + + nsresult RunSyncEventLoop(); + + PRBool IsUploadEvent(nsIDOMEvent* aEvent); + + nsresult DispatchPrematureAbortEvents(PRUint32 aType, + nsIDOMEventTarget* aTarget, + ProgressInfo* aProgressInfo); + + nsresult MaybeDispatchPrematureAbortEvents(PRBool aFromOpenRequest); + + // May be weak or strong, check mOwnedByXHR. + nsDOMWorkerXHR* mWorkerXHR; + nsCOMPtr mWorkerXHRWN; + + // May be weak or strong, check mOwnedByXHR. + nsIXMLHttpRequest* mXHR; + + // Always weak! + nsXMLHttpRequest* mConcreteXHR; + nsIXMLHttpRequestUpload* mUpload; + + nsCOMPtr mMainThread; + + nsRefPtr mLastXHRState; + nsRefPtr mLastProgressOrLoadEvent; + + SyncEventQueue* mSyncEventQueue; + + PRInt32 mChannelID; + + // Only touched on the worker thread! + nsCOMPtr mSyncXHRThread; + + // Touched on more than one thread, protected by the worker's lock. + nsRefPtr mSyncFinishedRunnable; + + nsAutoPtr mDownloadProgressInfo; + nsAutoPtr mUploadProgressInfo; + + // Whether or not this object is owned by the real XHR object. + PRPackedBool mOwnedByXHR; + + PRPackedBool mWantUploadListeners; + PRPackedBool mCanceled; + + PRPackedBool mSyncRequest; + +}; + +#endif /* __NSDOMWORKERXHRPROXY_H__ */ diff --git a/dom/workers/test/Makefile.in b/dom/src/threads/test/Makefile.in similarity index 91% rename from dom/workers/test/Makefile.in rename to dom/src/threads/test/Makefile.in index 6ac91e9e15ef..5a098c11ff87 100644 --- a/dom/workers/test/Makefile.in +++ b/dom/src/threads/test/Makefile.in @@ -36,12 +36,12 @@ # # ***** END LICENSE BLOCK ***** -DEPTH = ../../.. +DEPTH = ../../../.. topsrcdir = @top_srcdir@ srcdir = @srcdir@ VPATH = @srcdir@ -relativesrcdir = dom/workers/test +relativesrcdir = dom/src/threads/test include $(DEPTH)/config/autoconf.mk @@ -54,10 +54,10 @@ _TEST_FILES = \ test_close.html \ close_worker.js \ test_errorPropagation.html \ - errorPropagation_iframe.html \ - errorPropagation_worker.js \ - test_eventDispatch.html \ - eventDispatch_worker.js \ + errorPropagation_worker1.js \ + errorPropagation_worker2.js \ + test_functionHandlers.html \ + functionHandlers_worker.js \ test_importScripts.html \ importScripts_worker.js \ importScripts_worker_imported1.js \ @@ -74,12 +74,14 @@ _TEST_FILES = \ navigator_worker.js \ test_recursion.html \ recursion_worker.js \ - test_recursiveOnerror.html \ - recursiveOnerror_worker.js \ + test_regExpStatics.html \ + regExpStatics_worker.js \ test_relativeLoad.html \ relativeLoad_worker.js \ relativeLoad_worker2.js \ relativeLoad_import.js \ + test_scopeOnerror.html \ + scopeOnerror_worker.js \ test_suspend.html \ suspend_iframe.html \ suspend_worker.js \ @@ -107,8 +109,8 @@ _TEST_FILES = \ newError_worker.js \ test_chromeWorker.html \ WorkerTest_badworker.js \ - test_workersDisabled.html \ - workersDisabled_worker.js \ + test_xpcom.html \ + xpcom_worker.js \ $(NULL) _SUBDIR_TEST_FILES = \ @@ -119,14 +121,13 @@ _SUBDIR_TEST_FILES = \ _CHROME_TEST_FILES = \ test_chromeWorker.xul \ + test_chromeWorkerComponent.xul \ test_chromeWorkerJSM.xul \ WorkerTest.jsm \ WorkerTest_worker.js \ WorkerTest_subworker.js \ chromeWorker_worker.js \ chromeWorker_subworker.js \ - test_workersDisabled.xul \ - workersDisabled_worker.js \ $(NULL) ifneq ($(OS_ARCH),WINNT) diff --git a/dom/workers/Principal.cpp b/dom/src/threads/test/WorkerTest.jsm similarity index 69% rename from dom/workers/Principal.cpp rename to dom/src/threads/test/WorkerTest.jsm index 97c521d16404..a545fa436082 100644 --- a/dom/workers/Principal.cpp +++ b/dom/src/threads/test/WorkerTest.jsm @@ -1,4 +1,3 @@ -/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * @@ -12,15 +11,15 @@ * for the specific language governing rights and limitations under the * License. * - * The Original Code is Web Workers. + * The Original Code is DOM Worker Tests. * * The Initial Developer of the Original Code is - * The Mozilla Foundation. - * Portions created by the Initial Developer are Copyright (C) 2011 + * The Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): - * Ben Turner (Original Author) + * Ben Turner * * 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 @@ -36,39 +35,23 @@ * * ***** END LICENSE BLOCK ***** */ -#include "Principal.h" +let EXPORTED_SYMBOLS = [ + "WorkerTest" +]; -#include "jsapi.h" +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); -namespace { +// Define our lazy getters. +XPCOMUtils.defineLazyServiceGetter(this, "workerFactory", + "@mozilla.org/threads/workerfactory;1", + "nsIWorkerFactory"); -void -PrincipalDestroy(JSContext*, JSPrincipals*) -{ - // nothing -} - -JSBool -PrincipalSubsume(JSPrincipals*, JSPrincipals*) -{ - return JS_TRUE; -} - -const char gPrincipalCodebase[] = "Web Worker"; - -JSPrincipals gPrincipal = { - const_cast(gPrincipalCodebase), - NULL, NULL, 1, PrincipalDestroy, PrincipalSubsume +const WorkerTest = { + go: function(message, messageCallback, errorCallback) { + let worker = workerFactory.newChromeWorker("WorkerTest_worker.js"); + worker.onmessage = messageCallback; + worker.onerror = errorCallback; + worker.postMessage(message); + return worker; + } }; - -} // anonymous namespace - -BEGIN_WORKERS_NAMESPACE - -JSPrincipals* -GetWorkerPrincipal() -{ - return &gPrincipal; -} - -END_WORKERS_NAMESPACE diff --git a/dom/workers/Principal.h b/dom/src/threads/test/WorkerTest_badworker.js similarity index 77% rename from dom/workers/Principal.h rename to dom/src/threads/test/WorkerTest_badworker.js index 6ceed68962c6..f9f12c41f0c6 100644 --- a/dom/workers/Principal.h +++ b/dom/src/threads/test/WorkerTest_badworker.js @@ -1,4 +1,3 @@ -/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * @@ -12,15 +11,15 @@ * for the specific language governing rights and limitations under the * License. * - * The Original Code is Web Workers. + * The Original Code is DOM Worker Tests. * * The Initial Developer of the Original Code is - * The Mozilla Foundation. - * Portions created by the Initial Developer are Copyright (C) 2011 + * The Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): - * Ben Turner (Original Author) + * Ben Turner * * 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 @@ -36,18 +35,6 @@ * * ***** END LICENSE BLOCK ***** */ -#ifndef mozilla_dom_workers_principal_h__ -#define mozilla_dom_workers_principal_h__ - -#include "Workers.h" - -#include "jspubtd.h" - -BEGIN_WORKERS_NAMESPACE - -JSPrincipals* -GetWorkerPrincipal(); - -END_WORKERS_NAMESPACE - -#endif /* mozilla_dom_workers_principal_h__ */ +onmessage = function(event) { + throw "Shouldn't be able to read this!"; +} diff --git a/dom/workers/Events.h b/dom/src/threads/test/WorkerTest_subworker.js similarity index 53% rename from dom/workers/Events.h rename to dom/src/threads/test/WorkerTest_subworker.js index d96f827840ee..7c8aa2aeed4a 100644 --- a/dom/workers/Events.h +++ b/dom/src/threads/test/WorkerTest_subworker.js @@ -1,4 +1,3 @@ -/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * @@ -12,15 +11,15 @@ * for the specific language governing rights and limitations under the * License. * - * The Original Code is Web Workers. + * The Original Code is DOM Worker Tests. * * The Initial Developer of the Original Code is - * The Mozilla Foundation. - * Portions created by the Initial Developer are Copyright (C) 2011 + * The Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): - * Ben Turner (Original Author) + * Ben Turner * * 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 @@ -36,52 +35,42 @@ * * ***** END LICENSE BLOCK ***** */ -#ifndef mozilla_dom_workers_events_h__ -#define mozilla_dom_workers_events_h__ +onmessage = function(event) { + let chromeURL = event.data.replace("test_chromeWorkerJSM.xul", + "WorkerTest_badworker.js"); -#include "Workers.h" + let mochitestURL = event.data.replace("test_chromeWorkerJSM.xul", + "WorkerTest_badworker.js") + .replace("chrome://mochitests/content/chrome", + "http://mochi.test:8888/tests"); -#include "jspubtd.h" + // We should be able to XHR to anything we want, including a chrome URL. + let xhr = new XMLHttpRequest(); + xhr.open("GET", mochitestURL, false); + xhr.send(); -class JSAutoStructuredCloneBuffer; + if (!xhr.responseText) { + throw "Can't load script file via XHR!"; + } -BEGIN_WORKERS_NAMESPACE + // We shouldn't be able to make a ChromeWorker to a non-chrome URL. + let worker = new ChromeWorker(mochitestURL); + worker.onmessage = function(event) { + throw event.data; + }; + worker.onerror = function(event) { + event.preventDefault(); -namespace events { - -bool -InitClasses(JSContext* aCx, JSObject* aGlobal); - -JSObject* -CreateGenericEvent(JSContext* aCx, JSString* aType, bool aBubbles, - bool aCancelable); - -JSObject* -CreateMessageEvent(JSContext* aCx, JSAutoStructuredCloneBuffer& aData); - -JSObject* -CreateErrorEvent(JSContext* aCx, JSString* aMessage, JSString* aFilename, - uint32 aLineNumber); - -JSObject* -CreateProgressEvent(JSContext* aCx, JSString* aType, bool aLengthComputable, - jsdouble aLoaded, jsdouble aTotal); - -bool -IsSupportedEventClass(JSContext* aCx, JSObject* aEvent); - -bool -SetEventTarget(JSContext* aCx, JSObject* aEvent, JSObject* aTarget); - -bool -EventWasCanceled(JSContext* aCx, JSObject* aEvent); - -bool -DispatchEventToTarget(JSContext* aCx, JSObject* aTarget, JSObject* aEvent, - bool* aPreventDefaultCalled); - -} // namespace events - -END_WORKERS_NAMESPACE - -#endif /* mozilla_dom_workers_events_h__ */ + // And we shouldn't be able to make a regular Worker to a non-chrome URL. + worker = new Worker(mochitestURL); + worker.onmessage = function(event) { + throw event.data; + }; + worker.onerror = function(event) { + event.preventDefault(); + postMessage("Done"); + }; + worker.postMessage("Hi"); + }; + worker.postMessage("Hi"); +}; diff --git a/dom/workers/WorkerScope.h b/dom/src/threads/test/WorkerTest_worker.js similarity index 76% rename from dom/workers/WorkerScope.h rename to dom/src/threads/test/WorkerTest_worker.js index 20f5716ebf94..ddc356bb37c9 100644 --- a/dom/workers/WorkerScope.h +++ b/dom/src/threads/test/WorkerTest_worker.js @@ -1,4 +1,3 @@ -/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * @@ -12,15 +11,15 @@ * for the specific language governing rights and limitations under the * License. * - * The Original Code is Web Workers. + * The Original Code is DOM Worker Tests. * * The Initial Developer of the Original Code is - * The Mozilla Foundation. - * Portions created by the Initial Developer are Copyright (C) 2011 + * The Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): - * Ben Turner (Original Author) + * Ben Turner * * 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 @@ -36,18 +35,10 @@ * * ***** END LICENSE BLOCK ***** */ -#ifndef mozilla_dom_workers_workerscope_h__ -#define mozilla_dom_workers_workerscope_h__ - -#include "Workers.h" - -#include "jspubtd.h" - -BEGIN_WORKERS_NAMESPACE - -JSObject* -CreateDedicatedWorkerGlobalScope(JSContext* aCx); - -END_WORKERS_NAMESPACE - -#endif /* mozilla_dom_workers_workerscope_h__ */ +onmessage = function(event) { + let worker = new ChromeWorker("WorkerTest_subworker.js"); + worker.onmessage = function(event) { + postMessage(event.data); + } + worker.postMessage(event.data); +} diff --git a/dom/workers/test/atob_worker.js b/dom/src/threads/test/atob_worker.js similarity index 69% rename from dom/workers/test/atob_worker.js rename to dom/src/threads/test/atob_worker.js index a8f566299dda..3e453b43e1bb 100644 --- a/dom/workers/test/atob_worker.js +++ b/dom/src/threads/test/atob_worker.js @@ -1,7 +1,3 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ var data = [ -1, 0, 1, 1.5, null, undefined, true, false, "foo", "123456789012345", "1234567890123456", "12345678901234567"]; @@ -11,11 +7,11 @@ for (var i = 0; i < 30; i++) { str += i % 2 ? "b" : "a"; } -onmessage = function(event) { +function onmessage(event) { + data.forEach(function(string) { - var encoded = btoa(string); - postMessage({ type: "btoa", value: encoded }); - postMessage({ type: "atob", value: atob(encoded) }); + postMessage({ type: "btoa", value: btoa(string) }); + postMessage({ type: "atob", value: atob(btoa(string)) }); }); var threw; diff --git a/dom/workers/Navigator.h b/dom/src/threads/test/chromeWorker_subworker.js similarity index 74% rename from dom/workers/Navigator.h rename to dom/src/threads/test/chromeWorker_subworker.js index 4260ce50ecb6..401bcc48ee4f 100644 --- a/dom/workers/Navigator.h +++ b/dom/src/threads/test/chromeWorker_subworker.js @@ -1,4 +1,3 @@ -/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * @@ -12,15 +11,15 @@ * for the specific language governing rights and limitations under the * License. * - * The Original Code is Web Workers. + * The Original Code is DOM Worker Tests. * * The Initial Developer of the Original Code is - * The Mozilla Foundation. - * Portions created by the Initial Developer are Copyright (C) 2011 + * The Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): - * Ben Turner (Original Author) + * Ben Turner * * 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 @@ -36,25 +35,6 @@ * * ***** END LICENSE BLOCK ***** */ -#ifndef mozilla_dom_workers_navigator_h__ -#define mozilla_dom_workers_navigator_h__ - -#include "Workers.h" - -#include "jspubtd.h" - -BEGIN_WORKERS_NAMESPACE - -namespace navigator { - -bool -InitClass(JSContext* aCx, JSObject* aGlobal); - -JSObject* -Create(JSContext* aCx); - -} // namespace navigator - -END_WORKERS_NAMESPACE - -#endif // mozilla_dom_workers_navigator_h__ +onmessage = function(event) { + postMessage("Done!"); +}; diff --git a/dom/workers/ChromeWorkerScope.h b/dom/src/threads/test/chromeWorker_worker.js similarity index 64% rename from dom/workers/ChromeWorkerScope.h rename to dom/src/threads/test/chromeWorker_worker.js index ffcb75d32fda..fb7c2d04b6e3 100644 --- a/dom/workers/ChromeWorkerScope.h +++ b/dom/src/threads/test/chromeWorker_worker.js @@ -1,4 +1,3 @@ -/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * @@ -12,15 +11,15 @@ * for the specific language governing rights and limitations under the * License. * - * The Original Code is Web Workers. + * The Original Code is DOM Worker Tests. * * The Initial Developer of the Original Code is - * The Mozilla Foundation. - * Portions created by the Initial Developer are Copyright (C) 2011 + * The Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): - * Ben Turner (Original Author) + * Ben Turner * * 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 @@ -36,22 +35,34 @@ * * ***** END LICENSE BLOCK ***** */ -#ifndef mozilla_dom_workers_chromeworkerscope_h__ -#define mozilla_dom_workers_chromeworkerscope_h__ +// Test XPCOM.getService() +let threadMan = XPCOM.getService("@mozilla.org/thread-manager;1"); +let mainThread = threadMan.mainThread; +if (mainThread.isOnCurrentThread()) { + throw "Thread manager is lying to us!"; +} -#include "Workers.h" +// Test XPCOM.createInstance +let threadPool = XPCOM.createInstance("@mozilla.org/thread-pool;1"); +threadPool.shutdown(); -#include "jspubtd.h" +let notThreadsafe; +try { + notThreadsafe = XPCOM.createInstance("@mozilla.org/supports-PRBool;1"); +} +catch(e) { } -BEGIN_WORKERS_NAMESPACE +if (notThreadsafe) { + throw "Shouldn't be able to create non-threadsafe component!"; +} -namespace chromeworker { +function onmessage(event) { + // Test passing wrapped natives from the main thread. + event.data.shutdown(); -bool -DefineChromeWorkerFunctions(JSContext* aCx, JSObject* aGlobal); - -} // namespace chromeworker - -END_WORKERS_NAMESPACE - -#endif // mozilla_dom_workers_chromeworkerscope_h__ + let worker = new ChromeWorker("chromeWorker_subworker.js"); + worker.onmessage = function(event) { + postMessage(event.data); + } + worker.postMessage("Go"); +} diff --git a/dom/workers/test/closeOnGC_server.sjs b/dom/src/threads/test/closeOnGC_server.sjs similarity index 79% rename from dom/workers/test/closeOnGC_server.sjs rename to dom/src/threads/test/closeOnGC_server.sjs index 66e1fbf5581c..dbb652119a41 100644 --- a/dom/workers/test/closeOnGC_server.sjs +++ b/dom/src/threads/test/closeOnGC_server.sjs @@ -1,7 +1,3 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ function handleRequest(request, response) { response.setHeader("Content-Type", "text/plain", false); diff --git a/dom/workers/test/closeOnGC_worker.js b/dom/src/threads/test/closeOnGC_worker.js similarity index 56% rename from dom/workers/test/closeOnGC_worker.js rename to dom/src/threads/test/closeOnGC_worker.js index 9f8a26dcf4b2..495970aec598 100644 --- a/dom/workers/test/closeOnGC_worker.js +++ b/dom/src/threads/test/closeOnGC_worker.js @@ -1,7 +1,3 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ onclose = function() { var xhr = new XMLHttpRequest(); xhr.open("POST", "closeOnGC_server.sjs", false); diff --git a/dom/src/threads/test/close_worker.js b/dom/src/threads/test/close_worker.js new file mode 100644 index 000000000000..a7388bf671cd --- /dev/null +++ b/dom/src/threads/test/close_worker.js @@ -0,0 +1,5 @@ +onclose = function() { + postMessage("closed"); +}; + +setTimeout(function() { close(); }, 1000); diff --git a/dom/src/threads/test/errorPropagation_worker1.js b/dom/src/threads/test/errorPropagation_worker1.js new file mode 100644 index 000000000000..e09eda012386 --- /dev/null +++ b/dom/src/threads/test/errorPropagation_worker1.js @@ -0,0 +1,26 @@ +var worker = new Worker("errorPropagation_worker2.js"); + +var errorCount = 0; +worker.onerror = function(event) { + switch (errorCount++) { + case 0: + case 1: + // Let it propagate. + break; + case 2: + // Stop and rethrow. + event.preventDefault(); + throw event.message; + break; + case 3: + event.preventDefault(); + postMessage(event.message); + worker.onerror = null; + break; + default: + } +}; + +onmessage = function(event) { + worker.postMessage(event.data); +}; diff --git a/dom/src/threads/test/errorPropagation_worker2.js b/dom/src/threads/test/errorPropagation_worker2.js new file mode 100644 index 000000000000..d9ff989bfc16 --- /dev/null +++ b/dom/src/threads/test/errorPropagation_worker2.js @@ -0,0 +1,3 @@ +onmessage = function(event) { + throw event.data; +}; diff --git a/dom/src/threads/test/fibonacci_worker.js b/dom/src/threads/test/fibonacci_worker.js new file mode 100644 index 000000000000..591cf7594d8e --- /dev/null +++ b/dom/src/threads/test/fibonacci_worker.js @@ -0,0 +1,28 @@ +var results = []; + +function messageHandler(event) { + results.push(parseInt(event.data)); + if (results.length == 2) { + postMessage(results[0] + results[1]); + } +} + +function errorHandler(event) { + throw event.data; +} + +onmessage = function(event) { + var n = parseInt(event.data); + + if (n == 0 || n == 1) { + postMessage(n); + return; + } + + for (var i = 1; i <= 2; i++) { + var worker = new Worker("fibonacci_worker.js"); + worker.onmessage = messageHandler; + worker.onerror = errorHandler; + worker.postMessage(n - i); + } +} diff --git a/dom/src/threads/test/functionHandlers_worker.js b/dom/src/threads/test/functionHandlers_worker.js new file mode 100644 index 000000000000..f4ba64f3a5be --- /dev/null +++ b/dom/src/threads/test/functionHandlers_worker.js @@ -0,0 +1,8 @@ +function onmessage(event) { + throw event.data; +} + +function onerror(event) { + postMessage(event.message); + event.preventDefault(); +} diff --git a/dom/workers/test/importScripts_worker.js b/dom/src/threads/test/importScripts_worker.js similarity index 92% rename from dom/workers/test/importScripts_worker.js rename to dom/src/threads/test/importScripts_worker.js index 48660d1df686..e17f4539b348 100644 --- a/dom/workers/test/importScripts_worker.js +++ b/dom/src/threads/test/importScripts_worker.js @@ -1,7 +1,3 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ // Try no args. This shouldn't do anything. importScripts(); diff --git a/dom/workers/test/importScripts_worker_imported1.js b/dom/src/threads/test/importScripts_worker_imported1.js similarity index 64% rename from dom/workers/test/importScripts_worker_imported1.js rename to dom/src/threads/test/importScripts_worker_imported1.js index 9c33588c4386..338cce405920 100644 --- a/dom/workers/test/importScripts_worker_imported1.js +++ b/dom/src/threads/test/importScripts_worker_imported1.js @@ -1,7 +1,3 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ // This caused security exceptions in the past, make sure it doesn't! var myConstructor = {}.constructor; diff --git a/dom/workers/test/importScripts_worker_imported2.js b/dom/src/threads/test/importScripts_worker_imported2.js similarity index 64% rename from dom/workers/test/importScripts_worker_imported2.js rename to dom/src/threads/test/importScripts_worker_imported2.js index 3aafb60be396..b222f8a750d3 100644 --- a/dom/workers/test/importScripts_worker_imported2.js +++ b/dom/src/threads/test/importScripts_worker_imported2.js @@ -1,7 +1,3 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ // This caused security exceptions in the past, make sure it doesn't! var myConstructor2 = {}.constructor; diff --git a/dom/src/threads/test/importScripts_worker_imported3.js b/dom/src/threads/test/importScripts_worker_imported3.js new file mode 100644 index 000000000000..32093c582c0c --- /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) {} diff --git a/dom/src/threads/test/importScripts_worker_imported4.js b/dom/src/threads/test/importScripts_worker_imported4.js new file mode 100644 index 000000000000..b1fa7ceba9d8 --- /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!"); diff --git a/dom/workers/test/json_worker.js b/dom/src/threads/test/json_worker.js similarity index 91% rename from dom/workers/test/json_worker.js rename to dom/src/threads/test/json_worker.js index f35e14cf2baa..a54c8185dafb 100644 --- a/dom/workers/test/json_worker.js +++ b/dom/src/threads/test/json_worker.js @@ -1,7 +1,3 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ var cyclicalObject = {}; cyclicalObject.foo = cyclicalObject; @@ -36,12 +32,8 @@ objectWithSaneGetter2.prototype = { } }; -const throwingGetterThrownString = "bad"; - var objectWithThrowingGetter = { }; -objectWithThrowingGetter.__defineGetter__("foo", function() { - throw throwingGetterThrownString; -}); +objectWithThrowingGetter.__defineGetter__("foo", function() { throw "bad"; }); var typedArrayWithValues = new Int8Array(5); for (var index in typedArrayWithValues) { @@ -310,7 +302,7 @@ for (var index = 0; index < messages.length; index++) { } } -onmessage = function(event) { +function onmessage(event) { for (var index = 0; index < messages.length; index++) { var exception = undefined; @@ -318,13 +310,8 @@ onmessage = function(event) { postMessage(messages[index].value); } catch (e) { - if (e instanceof DOMException) { - if (e.code != DOMException.DATA_CLONE_ERR) { - throw "DOMException with the wrong code: " + e.code; - } - } - else if (e != throwingGetterThrownString) { - throw "Exception of the wrong type: " + e; + if (e.result != 2152923161) { + throw "Exception of the wrong type: " + e.result; } exception = e; } diff --git a/dom/src/threads/test/location_worker.js b/dom/src/threads/test/location_worker.js new file mode 100644 index 000000000000..a6b90e5f56ed --- /dev/null +++ b/dom/src/threads/test/location_worker.js @@ -0,0 +1,4 @@ +for (var string in self.location) { + postMessage({ "string": string, "value": self.location[string] }); +} +postMessage({"string": "testfinished", "value": self.location.toString()}); diff --git a/dom/workers/test/longThread_worker.js b/dom/src/threads/test/longThread_worker.js similarity index 66% rename from dom/workers/test/longThread_worker.js rename to dom/src/threads/test/longThread_worker.js index f132dd8c164d..d3afbd43ec43 100644 --- a/dom/workers/test/longThread_worker.js +++ b/dom/src/threads/test/longThread_worker.js @@ -1,7 +1,3 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ onmessage = function(event) { switch (event.data) { case "start": diff --git a/dom/workers/test/navigator_worker.js b/dom/src/threads/test/navigator_worker.js similarity index 85% rename from dom/workers/test/navigator_worker.js rename to dom/src/threads/test/navigator_worker.js index 2ca3b6fdeadf..632dfe32f28d 100644 --- a/dom/workers/test/navigator_worker.js +++ b/dom/src/threads/test/navigator_worker.js @@ -1,7 +1,3 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ var supportedProps = [ "appName", "appVersion", diff --git a/dom/src/threads/test/newError_worker.js b/dom/src/threads/test/newError_worker.js new file mode 100644 index 000000000000..83f287ad902a --- /dev/null +++ b/dom/src/threads/test/newError_worker.js @@ -0,0 +1 @@ +throw new Error("foo!"); diff --git a/dom/src/threads/test/recursion_worker.js b/dom/src/threads/test/recursion_worker.js new file mode 100644 index 000000000000..f9192a542366 --- /dev/null +++ b/dom/src/threads/test/recursion_worker.js @@ -0,0 +1,34 @@ +onerror = function(event) { + // Do nothing. +}; + +// Pure JS recursion +function recurse() { + recurse(); +} + +// JS -> C++ -> JS -> C++ recursion +function recurse2() { + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + xhr.open("GET", "nonexistent.file"); + } + xhr.open("GET", "nonexistent.file"); + postMessage("Done"); +} + +var count = 0; +onmessage = function(event) { + switch (++count) { + case 1: + recurse(); + break; + case 2: + recurse2(); + // Have to return here because we don't propagate exceptions from event + // handlers! + return; + default: + } + throw "Never should have gotten here!"; +} diff --git a/dom/src/threads/test/regExpStatics_worker.js b/dom/src/threads/test/regExpStatics_worker.js new file mode 100644 index 000000000000..cd82a43d32ca --- /dev/null +++ b/dom/src/threads/test/regExpStatics_worker.js @@ -0,0 +1,26 @@ +var runCount = 0; +var timeout; + +onmessage = function() { + run(); + timeout = setTimeout(run, 0); + timeout = setTimeout(run, 5000); +}; + +function run() { + if (RegExp.$1) { + throw "RegExp.$1 already set!"; + cancelTimeout(timeout); + } + + var match = /a(sd)f/.exec("asdf"); + if (!RegExp.$1) { + throw "RegExp.$1 didn't get set!"; + cancelTimeout(timeout); + } + + if (++runCount == 3) { + postMessage("done"); + } +} + diff --git a/dom/src/threads/test/relativeLoad_import.js b/dom/src/threads/test/relativeLoad_import.js new file mode 100644 index 000000000000..ea6f873621ac --- /dev/null +++ b/dom/src/threads/test/relativeLoad_import.js @@ -0,0 +1 @@ +const workerURL = "relativeLoad_worker.js"; diff --git a/dom/src/threads/test/relativeLoad_sub_import.js b/dom/src/threads/test/relativeLoad_sub_import.js new file mode 100644 index 000000000000..3cc288494efd --- /dev/null +++ b/dom/src/threads/test/relativeLoad_sub_import.js @@ -0,0 +1 @@ +const workerSubURL = "subdir/relativeLoad_sub_worker.js"; diff --git a/dom/workers/test/relativeLoad_sub_worker.js b/dom/src/threads/test/relativeLoad_sub_worker.js similarity index 83% rename from dom/workers/test/relativeLoad_sub_worker.js rename to dom/src/threads/test/relativeLoad_sub_worker.js index f725cfb6144a..894dbbb16360 100644 --- a/dom/workers/test/relativeLoad_sub_worker.js +++ b/dom/src/threads/test/relativeLoad_sub_worker.js @@ -1,7 +1,3 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ const importSubURL = "relativeLoad_sub_import.js"; onmessage = function(event) { diff --git a/dom/src/threads/test/relativeLoad_sub_worker2.js b/dom/src/threads/test/relativeLoad_sub_worker2.js new file mode 100644 index 000000000000..182be7e13c55 --- /dev/null +++ b/dom/src/threads/test/relativeLoad_sub_worker2.js @@ -0,0 +1,5 @@ +const importSubURL = "relativeLoad_sub_import.js"; + +importScripts(importSubURL); + +postMessage(workerSubURL); diff --git a/dom/workers/test/relativeLoad_worker.js b/dom/src/threads/test/relativeLoad_worker.js similarity index 80% rename from dom/workers/test/relativeLoad_worker.js rename to dom/src/threads/test/relativeLoad_worker.js index cec92a644ecd..b416d8f7b47d 100644 --- a/dom/workers/test/relativeLoad_worker.js +++ b/dom/src/threads/test/relativeLoad_worker.js @@ -1,7 +1,3 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ const importURL = "relativeLoad_import.js"; onmessage = function(event) { @@ -16,7 +12,7 @@ onmessage = function(event) { importScripts(importURL); var worker = new Worker("relativeLoad_worker2.js"); worker.onerror = function(event) { - throw event.message; + throw event.data; }; worker.onmessage = function(event) { if (event.data != workerURL) { diff --git a/dom/src/threads/test/relativeLoad_worker2.js b/dom/src/threads/test/relativeLoad_worker2.js new file mode 100644 index 000000000000..0703401b70c6 --- /dev/null +++ b/dom/src/threads/test/relativeLoad_worker2.js @@ -0,0 +1,5 @@ +const importURL = "relativeLoad_import.js"; + +importScripts(importURL); + +postMessage(workerURL); diff --git a/dom/src/threads/test/scopeOnerror_worker.js b/dom/src/threads/test/scopeOnerror_worker.js new file mode 100644 index 000000000000..9e8bf41b1cb0 --- /dev/null +++ b/dom/src/threads/test/scopeOnerror_worker.js @@ -0,0 +1,26 @@ +if (onerror !== undefined || typeof(onerror) != "undefined") { + throw "Undefined onerror has bad type!"; +} + +onerror = function(event) { + if (!event.cancelable) { + throw "Error event is not cancelable!"; + } + + if (event.target != this) { + throw "Error event is targeted at the wrong object!"; + } + + if (event.message == "uncaught exception: This error should not make it back out") { + event.preventDefault(); + postMessage("Done"); + } +} + +if (onerror === undefined || typeof(onerror) == "undefined") { + throw "onerror has a bad type!"; +} + +onmessage = function(event) { + throw event.data; +} diff --git a/dom/workers/test/simpleThread_worker.js b/dom/src/threads/test/simpleThread_worker.js similarity index 54% rename from dom/workers/test/simpleThread_worker.js rename to dom/src/threads/test/simpleThread_worker.js index 9aa8bd1019a9..64e24c2eb274 100644 --- a/dom/workers/test/simpleThread_worker.js +++ b/dom/src/threads/test/simpleThread_worker.js @@ -1,21 +1,4 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ function messageListener(event) { - var exception; - try { - event.bubbles = true; - } - catch(e) { - exception = e; - } - - if (!(exception instanceof TypeError) || - exception.message != "setting a property that has only a getter") { - throw exception; - } - switch (event.data) { case "no-op": break; @@ -27,6 +10,7 @@ function messageListener(event) { postMessage("started"); break; case "stop": + for (var i = 0; i < 1000; i++) { } self.postMessage('no-op'); postMessage("stopped"); self.removeEventListener("message", messageListener, false); @@ -36,4 +20,4 @@ function messageListener(event) { } } -addEventListener("message", { handleEvent: messageListener }); +addEventListener("message", messageListener, false); diff --git a/dom/workers/test/suspend_iframe.html b/dom/src/threads/test/suspend_iframe.html similarity index 71% rename from dom/workers/test/suspend_iframe.html rename to dom/src/threads/test/suspend_iframe.html index 9915395ecfd9..0fbbb91c8d28 100644 --- a/dom/workers/test/suspend_iframe.html +++ b/dom/src/threads/test/suspend_iframe.html @@ -1,8 +1,4 @@ - - + Test for DOM Worker Threads Suspending @@ -19,18 +15,16 @@ function terminateWorker() { if (worker) { - worker.postMessage("stop"); + worker.terminate(); worker = null; } } function startWorker(messageCallback, errorCallback) { - var lastData; worker = new Worker("suspend_worker.js"); worker.onmessage = function(event) { - output.textContent = (lastData ? lastData + " -> " : "") + event.data; - lastData = event.data; + output.textContent = output.textContent + event.data + "\n"; messageCallback(event.data); }; diff --git a/dom/src/threads/test/suspend_worker.js b/dom/src/threads/test/suspend_worker.js new file mode 100644 index 000000000000..04569bfbd1a4 --- /dev/null +++ b/dom/src/threads/test/suspend_worker.js @@ -0,0 +1,5 @@ +var counter = 0; + +setInterval(function() { + postMessage(++counter); +}, 100); diff --git a/dom/workers/test/terminate_worker.js b/dom/src/threads/test/terminate_worker.js similarity index 62% rename from dom/workers/test/terminate_worker.js rename to dom/src/threads/test/terminate_worker.js index ad4e8252773d..02ffb83c373b 100644 --- a/dom/workers/test/terminate_worker.js +++ b/dom/src/threads/test/terminate_worker.js @@ -1,7 +1,3 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ onclose = function() { postMessage("Closed!"); } diff --git a/dom/workers/test/testXHR.txt b/dom/src/threads/test/testXHR.txt similarity index 100% rename from dom/workers/test/testXHR.txt rename to dom/src/threads/test/testXHR.txt diff --git a/dom/workers/test/test_404.html b/dom/src/threads/test/test_404.html similarity index 88% rename from dom/workers/test/test_404.html rename to dom/src/threads/test/test_404.html index 932c5c76f658..04d659495ff2 100644 --- a/dom/workers/test/test_404.html +++ b/dom/src/threads/test/test_404.html @@ -1,7 +1,3 @@ - diff --git a/dom/workers/test/test_chromeWorker.html b/dom/src/threads/test/test_chromeWorker.html similarity index 65% rename from dom/workers/test/test_chromeWorker.html rename to dom/src/threads/test/test_chromeWorker.html index bca06e2214a5..d33d0cd3022c 100644 --- a/dom/workers/test/test_chromeWorker.html +++ b/dom/src/threads/test/test_chromeWorker.html @@ -1,7 +1,3 @@ - Test for DOM Worker Threads @@ -21,6 +17,16 @@ ok(true, "ChromeWorker constructor wasn't blocked!"); } + try { + var worker = Components.classes["@mozilla.org/threads/workerfactory;1"] + .createInstance(Components.interfaces.nsIWorkerFactory) + .newChromeWorker("simpleThread_worker.js"); + ok(false, "nsIWorkerFactory should be blocked!"); + } + catch (e) { + ok(true, "nsIWorkerFactory wasn't blocked!"); + } + diff --git a/dom/src/threads/test/test_chromeWorker.xul b/dom/src/threads/test/test_chromeWorker.xul new file mode 100644 index 000000000000..071d742dbda2 --- /dev/null +++ b/dom/src/threads/test/test_chromeWorker.xul @@ -0,0 +1,97 @@ + + + + + + + + +

+ +

+  
+  
diff --git a/dom/src/threads/test/test_chromeWorkerComponent.xul b/dom/src/threads/test/test_chromeWorkerComponent.xul new file mode 100644 index 000000000000..ad8f2eabdf24 --- /dev/null +++ b/dom/src/threads/test/test_chromeWorkerComponent.xul @@ -0,0 +1,88 @@ + + + + + + + + +

+ +

+  
+  
diff --git a/dom/src/threads/test/test_chromeWorkerJSM.xul b/dom/src/threads/test/test_chromeWorkerJSM.xul new file mode 100644 index 000000000000..4626990de604 --- /dev/null +++ b/dom/src/threads/test/test_chromeWorkerJSM.xul @@ -0,0 +1,90 @@ + + + + + + + + +

+ +

+  
+  
diff --git a/dom/workers/test/test_close.html b/dom/src/threads/test/test_close.html similarity index 85% rename from dom/workers/test/test_close.html rename to dom/src/threads/test/test_close.html index b2af6726068c..15fd2b1bafc2 100644 --- a/dom/workers/test/test_close.html +++ b/dom/src/threads/test/test_close.html @@ -1,7 +1,3 @@ - diff --git a/dom/workers/test/test_closeOnGC.html b/dom/src/threads/test/test_closeOnGC.html similarity index 91% rename from dom/workers/test/test_closeOnGC.html rename to dom/src/threads/test/test_closeOnGC.html index aeb0b1106488..cbdb39dbce35 100644 --- a/dom/workers/test/test_closeOnGC.html +++ b/dom/src/threads/test/test_closeOnGC.html @@ -1,7 +1,3 @@ - diff --git a/dom/src/threads/test/test_errorPropagation.html b/dom/src/threads/test/test_errorPropagation.html new file mode 100644 index 000000000000..767bc8e97594 --- /dev/null +++ b/dom/src/threads/test/test_errorPropagation.html @@ -0,0 +1,107 @@ + + + + + Test for DOM Worker Threads + + + + + +
+
+
+ + + diff --git a/dom/workers/test/test_fibonacci.html b/dom/src/threads/test/test_fibonacci.html similarity index 76% rename from dom/workers/test/test_fibonacci.html rename to dom/src/threads/test/test_fibonacci.html index fa2b4d0de018..6fd87480d4a0 100644 --- a/dom/workers/test/test_fibonacci.html +++ b/dom/src/threads/test/test_fibonacci.html @@ -1,7 +1,3 @@ - + + Test for DOM Worker Threads Recursion + + + + + +

+ +
+
+
+ + diff --git a/dom/workers/test/test_importScripts.html b/dom/src/threads/test/test_importScripts.html similarity index 91% rename from dom/workers/test/test_importScripts.html rename to dom/src/threads/test/test_importScripts.html index 4364ca494aab..29a9cadb313d 100644 --- a/dom/workers/test/test_importScripts.html +++ b/dom/src/threads/test/test_importScripts.html @@ -1,7 +1,3 @@ - Test for DOM Worker Threads @@ -22,7 +18,6 @@ worker.onerror = function(event) { is(event.message, "foo!", "Got wrong error message!"); - event.preventDefault(); SimpleTest.finish(); } diff --git a/dom/workers/test/test_recursion.html b/dom/src/threads/test/test_recursion.html similarity index 57% rename from dom/workers/test/test_recursion.html rename to dom/src/threads/test/test_recursion.html index a90481455225..cd870dd74a63 100644 --- a/dom/workers/test/test_recursion.html +++ b/dom/src/threads/test/test_recursion.html @@ -1,7 +1,3 @@ - + + Test for DOM Worker Threads RegExp statics + + + + + +

+ +
+
+
+ + diff --git a/dom/workers/test/test_relativeLoad.html b/dom/src/threads/test/test_relativeLoad.html similarity index 85% rename from dom/workers/test/test_relativeLoad.html rename to dom/src/threads/test/test_relativeLoad.html index 4cb7652e588d..73976841625b 100644 --- a/dom/workers/test/test_relativeLoad.html +++ b/dom/src/threads/test/test_relativeLoad.html @@ -1,7 +1,3 @@ - + + Test for DOM Worker Threads + + + + + +
+
+
+ + + diff --git a/dom/workers/test/test_simpleThread.html b/dom/src/threads/test/test_simpleThread.html similarity index 90% rename from dom/workers/test/test_simpleThread.html rename to dom/src/threads/test/test_simpleThread.html index b30d45cd0f3f..1f4c885834ab 100644 --- a/dom/workers/test/test_simpleThread.html +++ b/dom/src/threads/test/test_simpleThread.html @@ -1,7 +1,3 @@ - @@ -100,9 +96,9 @@ if (!suspended) { ok(lastCount === undefined || lastCount == data - 1, - "Got good data, lastCount = " + lastCount + ", data = " + data); + "Data is inconsistent"); lastCount = data; - if (lastCount == 25) { + if (lastCount == 50) { setCachePref(true); iframe.location = "about:blank"; // We want suspend_iframe.html to go into bfcache, so we need to flush diff --git a/dom/workers/test/test_terminate.html b/dom/src/threads/test/test_terminate.html similarity index 91% rename from dom/workers/test/test_terminate.html rename to dom/src/threads/test/test_terminate.html index 548fc60b8622..f9c03ccf222a 100644 --- a/dom/workers/test/test_terminate.html +++ b/dom/src/threads/test/test_terminate.html @@ -1,7 +1,3 @@ - - - - - - - diff --git a/dom/workers/test/errorPropagation_worker.js b/dom/workers/test/errorPropagation_worker.js deleted file mode 100644 index 84f5916e0fba..000000000000 --- a/dom/workers/test/errorPropagation_worker.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ -var seenScopeError; -onerror = function(message, filename, lineno) { - if (!seenScopeError) { - seenScopeError = true; - postMessage({ - type: "scope", - data: { message: message, filename: filename, lineno: lineno } - }); - return true; - } -}; - -onmessage = function(event) { - var workerId = parseInt(event.data); - - if (workerId > 1) { - var worker = new Worker("errorPropagation_worker.js"); - - worker.onmessage = function(event) { - postMessage(event.data); - }; - - var seenWorkerError; - worker.onerror = function(event) { - if (!seenWorkerError) { - seenWorkerError = true; - postMessage({ - type: "worker", - data: { - message: event.message, - filename: event.filename, - lineno: event.lineno - } - }); - event.preventDefault(); - } - }; - - worker.postMessage(workerId - 1); - return; - } - - var interval = setInterval(function() { - throw new Error("expectedError"); - }, 100); -}; diff --git a/dom/workers/test/eventDispatch_worker.js b/dom/workers/test/eventDispatch_worker.js deleted file mode 100644 index 586eeb95e657..000000000000 --- a/dom/workers/test/eventDispatch_worker.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ -const fakeEventType = "foo"; - -function testEventTarget(event) { - if (event.target !== null || event.currentTarget !== null) { - throw new Error("Event has a non-null target!"); - } - postMessage(event.data); -} - -addEventListener(fakeEventType, function(event) { - throw new Error("Trusted event listener received untrusted event!"); -}, false); - -addEventListener(fakeEventType, function(event) { - if (event.target !== self || event.currentTarget !== self) { - throw new Error("Fake event has bad target!"); - } - if (event.isTrusted) { - throw new Error("Event should be untrusted!"); - } - postMessage(event.data); -}, false, true); - -var count = 0; -onmessage = function(event) { - if (event.target !== self || event.currentTarget !== self) { - throw new Error("Event has bad target!"); - } - - if (!count++) { - var exception; - try { - self.dispatchEvent(event); - } - catch(e) { - exception = e; - } - - if (!exception) { - throw new Error("Recursive dispatch didn't fail!"); - } - - event.initMessageEvent(fakeEventType, event.bubbles, event.cancelable, - event.data, "*", null); - self.dispatchEvent(event); - return; - } - - setTimeout(testEventTarget, 0, event); -}; diff --git a/dom/workers/test/fibonacci_worker.js b/dom/workers/test/fibonacci_worker.js deleted file mode 100644 index fa35385e789a..000000000000 --- a/dom/workers/test/fibonacci_worker.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ -onmessage = function(event) { - var n = parseInt(event.data); - - if (n < 2) { - postMessage(n); - return; - } - - var results = []; - for (var i = 1; i <= 2; i++) { - var worker = new Worker("fibonacci_worker.js"); - worker.onmessage = function(event) { - results.push(parseInt(event.data)); - if (results.length == 2) { - postMessage(results[0] + results[1]); - } - }; - worker.postMessage(n - i); - } -} diff --git a/dom/workers/test/importScripts_worker_imported3.js b/dom/workers/test/importScripts_worker_imported3.js deleted file mode 100644 index c54be3e5f7ff..000000000000 --- a/dom/workers/test/importScripts_worker_imported3.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ -// Deliberate syntax error, should generate a worker exception! -for (var index = 0; index < 100) {} diff --git a/dom/workers/test/importScripts_worker_imported4.js b/dom/workers/test/importScripts_worker_imported4.js deleted file mode 100644 index 82f8708c5977..000000000000 --- a/dom/workers/test/importScripts_worker_imported4.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ -// Deliberate throw, should generate a worker exception! -throw new Error("Bah!"); diff --git a/dom/workers/test/location_worker.js b/dom/workers/test/location_worker.js deleted file mode 100644 index 6786d805f0f5..000000000000 --- a/dom/workers/test/location_worker.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ -for (var string in self.location) { - postMessage({ "string": string, "value": self.location[string] }); -} -dump(self.location + " \n"); -postMessage({ "string": "testfinished", "value": self.location.toString() }); diff --git a/dom/workers/test/newError_worker.js b/dom/workers/test/newError_worker.js deleted file mode 100644 index 46e6226f7341..000000000000 --- a/dom/workers/test/newError_worker.js +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ -throw new Error("foo!"); diff --git a/dom/workers/test/recursion_worker.js b/dom/workers/test/recursion_worker.js deleted file mode 100644 index 1a61ec9d1a55..000000000000 --- a/dom/workers/test/recursion_worker.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -// This function should never run on a too much recursion error. -onerror = function(event) { - postMessage(event.message); -}; - -// Pure JS recursion -function recurse() { - recurse(); -} - -// JS -> C++ -> JS -> C++ recursion -function recurse2() { - var xhr = new XMLHttpRequest(); - xhr.onreadystatechange = function() { - xhr.open("GET", "nonexistent.file"); - } - xhr.open("GET", "nonexistent.file"); -} - -var messageCount = 0; -onmessage = function(event) { - switch (++messageCount) { - case 2: - recurse2(); - - // An exception thrown from an event handler like xhr.onreadystatechange - // should not leave an exception pending in the code that generated the - // event. - postMessage("Done"); - return; - - case 1: - recurse(); - throw "Exception should have prevented us from getting here!"; - - default: - throw "Weird number of messages: " + messageCount; - } - - throw "Impossible to get here!"; -} diff --git a/dom/workers/test/recursiveOnerror_worker.js b/dom/workers/test/recursiveOnerror_worker.js deleted file mode 100644 index e6656d68f8de..000000000000 --- a/dom/workers/test/recursiveOnerror_worker.js +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ -onerror = function(message, filename, lineno) { - throw new Error("2"); -}; - -onmessage = function(event) { - throw new Error("1"); -}; diff --git a/dom/workers/test/relativeLoad_import.js b/dom/workers/test/relativeLoad_import.js deleted file mode 100644 index 114d1883cdcc..000000000000 --- a/dom/workers/test/relativeLoad_import.js +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ -const workerURL = "relativeLoad_worker.js"; diff --git a/dom/workers/test/relativeLoad_sub_import.js b/dom/workers/test/relativeLoad_sub_import.js deleted file mode 100644 index cac2a6f04148..000000000000 --- a/dom/workers/test/relativeLoad_sub_import.js +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ -const workerSubURL = "subdir/relativeLoad_sub_worker.js"; diff --git a/dom/workers/test/relativeLoad_sub_worker2.js b/dom/workers/test/relativeLoad_sub_worker2.js deleted file mode 100644 index f47a29ee69e5..000000000000 --- a/dom/workers/test/relativeLoad_sub_worker2.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ -const importSubURL = "relativeLoad_sub_import.js"; - -importScripts(importSubURL); - -postMessage(workerSubURL); diff --git a/dom/workers/test/relativeLoad_worker2.js b/dom/workers/test/relativeLoad_worker2.js deleted file mode 100644 index e9fbdf903ae4..000000000000 --- a/dom/workers/test/relativeLoad_worker2.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ -const importURL = "relativeLoad_import.js"; - -importScripts(importURL); - -postMessage(workerURL); diff --git a/dom/workers/test/suspend_worker.js b/dom/workers/test/suspend_worker.js deleted file mode 100644 index 43eb24a7ad33..000000000000 --- a/dom/workers/test/suspend_worker.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ -var counter = 0; - -var interval = setInterval(function() { - postMessage(++counter); -}, 100); - -onmessage = function(event) { - clearInterval(interval); -} diff --git a/dom/workers/test/test_chromeWorker.xul b/dom/workers/test/test_chromeWorker.xul deleted file mode 100644 index 3651c2ce6def..000000000000 --- a/dom/workers/test/test_chromeWorker.xul +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - -

- -

-  
-  
diff --git a/dom/workers/test/test_chromeWorkerJSM.xul b/dom/workers/test/test_chromeWorkerJSM.xul deleted file mode 100644 index 1b94f2a879ea..000000000000 --- a/dom/workers/test/test_chromeWorkerJSM.xul +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - -

- -

-  
-  
diff --git a/dom/workers/test/test_errorPropagation.html b/dom/workers/test/test_errorPropagation.html deleted file mode 100644 index 46ed3c643e35..000000000000 --- a/dom/workers/test/test_errorPropagation.html +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - diff --git a/dom/workers/test/test_eventDispatch.html b/dom/workers/test/test_eventDispatch.html deleted file mode 100644 index 08eb48e1dbda..000000000000 --- a/dom/workers/test/test_eventDispatch.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - diff --git a/dom/workers/test/test_recursiveOnerror.html b/dom/workers/test/test_recursiveOnerror.html deleted file mode 100644 index 858ab5f8710a..000000000000 --- a/dom/workers/test/test_recursiveOnerror.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - diff --git a/dom/workers/test/test_workersDisabled.html b/dom/workers/test/test_workersDisabled.html deleted file mode 100644 index abe50a036585..000000000000 --- a/dom/workers/test/test_workersDisabled.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - diff --git a/dom/workers/test/test_workersDisabled.xul b/dom/workers/test/test_workersDisabled.xul deleted file mode 100644 index 1e3237119f7a..000000000000 --- a/dom/workers/test/test_workersDisabled.xul +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - -

- -

-  
-  
diff --git a/dom/workers/test/threadErrors_worker1.js b/dom/workers/test/threadErrors_worker1.js deleted file mode 100644 index c0ddade82c0f..000000000000 --- a/dom/workers/test/threadErrors_worker1.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ -// Syntax error -onmessage = function(event) { - for (var i = 0; i < 10) { } -} diff --git a/dom/workers/test/threadErrors_worker2.js b/dom/workers/test/threadErrors_worker2.js deleted file mode 100644 index 0462d9668270..000000000000 --- a/dom/workers/test/threadErrors_worker2.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ -// Bad function error -onmessage = function(event) { - foopy(); -} diff --git a/dom/workers/test/threadErrors_worker3.js b/dom/workers/test/threadErrors_worker3.js deleted file mode 100644 index 151ffbe5e422..000000000000 --- a/dom/workers/test/threadErrors_worker3.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ -// Unhandled exception in body -onmessage = function(event) { -}; - -throw new Error("Bah!"); diff --git a/dom/workers/test/threadErrors_worker4.js b/dom/workers/test/threadErrors_worker4.js deleted file mode 100644 index a1538e94f3ac..000000000000 --- a/dom/workers/test/threadErrors_worker4.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ -// Throwing message listener -onmessage = function(event) { - throw new Error("Bah!"); -}; diff --git a/dom/workers/test/workersDisabled_worker.js b/dom/workers/test/workersDisabled_worker.js deleted file mode 100644 index 7346fc142c34..000000000000 --- a/dom/workers/test/workersDisabled_worker.js +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ -onmessage = function(event) { - postMessage(event.data); -} diff --git a/dom/workers/test/xhrAbort_worker.js b/dom/workers/test/xhrAbort_worker.js deleted file mode 100644 index 763189d3d578..000000000000 --- a/dom/workers/test/xhrAbort_worker.js +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ -function runTest() { - var xhr = new XMLHttpRequest(); - - var events = []; - function pushEvent(event) { - var readyState, responseText, status, statusText; - - try { - readyState = xhr.readyState; - } - catch (e) { - readyState = "[exception]"; - } - - try { - responseText = xhr.responseText; - } - catch (e) { - responseText = "[exception]"; - } - - try { - status = xhr.status; - } - catch (e) { - status = "[exception]"; - } - - try { - statusText = xhr.statusText; - } - catch (e) { - statusText = "[exception]"; - } - - var str = event.type + "(" + readyState + ", '" + responseText + "', " + - status + ", '" + statusText + "'"; - if (event instanceof ProgressEvent) { - str += ", progressEvent"; - } - str += ")"; - - events.push(str); - } - - xhr.onerror = function(event) { - throw new Error("Error: " + xhr.statusText); - } - - xhr.onload = function(event) { - throw new Error("Shouldn't have gotten load event!"); - }; - - var seenAbort; - xhr.onabort = function(event) { - if (seenAbort) { - throw new Error("Already seen the abort event!"); - } - seenAbort = true; - - pushEvent(event); - postMessage(events); - }; - - var count = 0; - xhr.onreadystatechange = function(event) { - pushEvent(event); - if (++count == 3) { - xhr.abort(); - } - }; - - xhr.open("GET", "testXHR.txt"); - xhr.send(null); -} - -function messageListener(event) { - switch (event.data) { - case "start": - runTest(); - break; - default: - throw new Error("Bad message!"); - } -} - -addEventListener("message", messageListener, false); diff --git a/js/src/shell/jsworkers.h b/js/src/shell/jsworkers.h index 99d07ed7b079..406e6df0e6cd 100644 --- a/js/src/shell/jsworkers.h +++ b/js/src/shell/jsworkers.h @@ -48,7 +48,7 @@ /* * Workers for the JS shell. * - * Note: The real implementation of DOM Workers is in dom/workers. + * Note: The real implementation of DOM Workers is in dom/src/threads. */ namespace js { namespace workers { diff --git a/js/src/xpconnect/src/xpcruntimesvc.cpp b/js/src/xpconnect/src/xpcruntimesvc.cpp index 1343fd479cca..b91e65545ba5 100644 --- a/js/src/xpconnect/src/xpcruntimesvc.cpp +++ b/js/src/xpconnect/src/xpcruntimesvc.cpp @@ -41,9 +41,6 @@ #include "xpcprivate.h" -#include "mozilla/dom/workers/Workers.h" -using mozilla::dom::workers::ResolveWorkerClasses; - NS_INTERFACE_MAP_BEGIN(BackstagePass) NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) NS_INTERFACE_MAP_ENTRY(nsIClassInfo) @@ -75,17 +72,9 @@ BackstagePass::NewResolve(nsIXPConnectWrappedNative *wrapper, { JSBool resolved; - *_retval = !!JS_ResolveStandardClass(cx, obj, id, &resolved); - if(!*_retval) - return NS_OK; - - if(resolved) - { + *_retval = JS_ResolveStandardClass(cx, obj, id, &resolved); + if(*_retval && resolved) *objp = obj; - return NS_OK; - } - - *_retval = !!ResolveWorkerClasses(cx, obj, id, flags, objp); return NS_OK; } diff --git a/layout/build/Makefile.in b/layout/build/Makefile.in index 340216d07d75..410fa1eb6d35 100644 --- a/layout/build/Makefile.in +++ b/layout/build/Makefile.in @@ -103,7 +103,7 @@ SHARED_LIBRARY_LIBS = \ $(DEPTH)/dom/src/geolocation/$(LIB_PREFIX)jsdomgeolocation_s.$(LIB_SUFFIX) \ $(DEPTH)/dom/src/notification/$(LIB_PREFIX)jsdomnotification_s.$(LIB_SUFFIX) \ $(DEPTH)/dom/system/$(LIB_PREFIX)domsystem_s.$(LIB_SUFFIX) \ - $(DEPTH)/dom/workers/$(LIB_PREFIX)domworkers_s.$(LIB_SUFFIX) \ + $(DEPTH)/dom/src/threads/$(LIB_PREFIX)domthreads_s.$(LIB_SUFFIX) \ $(DEPTH)/dom/indexedDB/$(LIB_PREFIX)dom_indexeddb_s.$(LIB_SUFFIX) \ $(DEPTH)/editor/libeditor/text/$(LIB_PREFIX)texteditor_s.$(LIB_SUFFIX) \ $(DEPTH)/editor/libeditor/base/$(LIB_PREFIX)editorbase_s.$(LIB_SUFFIX) \ @@ -274,6 +274,7 @@ LOCAL_INCLUDES += -I$(srcdir)/../base \ -I$(topsrcdir)/dom/src/storage \ -I$(topsrcdir)/dom/src/offline \ -I$(topsrcdir)/dom/src/geolocation \ + -I$(topsrcdir)/dom/src/threads \ -I. \ -I$(topsrcdir)/editor/libeditor/base \ -I$(topsrcdir)/editor/libeditor/text \ diff --git a/layout/build/nsLayoutModule.cpp b/layout/build/nsLayoutModule.cpp index d0e914f934e4..f8f0c95ad4cd 100644 --- a/layout/build/nsLayoutModule.cpp +++ b/layout/build/nsLayoutModule.cpp @@ -106,6 +106,7 @@ #include "nsXMLHttpRequest.h" #include "nsChannelPolicy.h" #include "nsWebSocket.h" +#include "nsDOMWorker.h" #include "nsEventSource.h" // view stuff @@ -322,6 +323,7 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(IndexedDatabaseManager, NS_GENERIC_FACTORY_CONSTRUCTOR(nsDeviceMotionSystem) #endif NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(ThirdPartyUtil, Init) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsWorkerFactory) #if defined(ANDROID) || defined(MOZ_PLATFORM_MAEMO) NS_GENERIC_FACTORY_CONSTRUCTOR(nsHapticFeedback) #endif @@ -860,6 +862,7 @@ NS_DEFINE_NAMED_CID(NS_SYSTEMPRINCIPAL_CID); NS_DEFINE_NAMED_CID(NS_NULLPRINCIPAL_CID); NS_DEFINE_NAMED_CID(NS_SECURITYNAMESET_CID); NS_DEFINE_NAMED_CID(THIRDPARTYUTIL_CID); +NS_DEFINE_NAMED_CID(NS_WORKERFACTORY_CID); NS_DEFINE_NAMED_CID(NS_STRUCTUREDCLONECONTAINER_CID); #if defined(XP_UNIX) || \ @@ -1012,6 +1015,7 @@ static const mozilla::Module::CIDEntry kLayoutCIDs[] = { { &kNS_HAPTICFEEDBACK_CID, false, NULL, nsHapticFeedbackConstructor }, #endif { &kTHIRDPARTYUTIL_CID, false, NULL, ThirdPartyUtilConstructor }, + { &kNS_WORKERFACTORY_CID, false, NULL, nsWorkerFactoryConstructor }, { &kNS_STRUCTUREDCLONECONTAINER_CID, false, NULL, nsStructuredCloneContainerConstructor }, { NULL } }; @@ -1149,6 +1153,7 @@ static const mozilla::Module::ContractIDEntry kLayoutContracts[] = { { "@mozilla.org/widget/hapticfeedback;1", &kNS_HAPTICFEEDBACK_CID }, #endif { THIRDPARTYUTIL_CONTRACTID, &kTHIRDPARTYUTIL_CID }, + { NS_WORKERFACTORY_CONTRACTID, &kNS_WORKERFACTORY_CID }, { NS_STRUCTUREDCLONECONTAINER_CONTRACTID, &kNS_STRUCTUREDCLONECONTAINER_CID }, { NULL } }; diff --git a/layout/build/nsLayoutStatics.cpp b/layout/build/nsLayoutStatics.cpp index 7495988ee159..89f5d10916d5 100644 --- a/layout/build/nsLayoutStatics.cpp +++ b/layout/build/nsLayoutStatics.cpp @@ -79,6 +79,7 @@ #include "nsTextFragment.h" #include "nsCSSRuleProcessor.h" #include "nsCrossSiteListenerProxy.h" +#include "nsDOMThreadService.h" #include "nsHTMLDNSPrefetch.h" #include "nsHtml5Module.h" #include "nsCrossSiteListenerProxy.h" @@ -343,6 +344,8 @@ nsLayoutStatics::Shutdown() nsHTMLEditor::Shutdown(); nsTextServicesDocument::Shutdown(); + nsDOMThreadService::Shutdown(); + #ifdef MOZ_SYDNEYAUDIO nsAudioStream::ShutdownLibrary(); #endif diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index a148f659ee13..e597a2ac90c5 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -97,11 +97,6 @@ pref("dom.indexedDB.enabled", true); // Space to allow indexedDB databases before prompting (in MB). pref("dom.indexedDB.warningQuota", 50); -// Whether or not Web Workers are enabled. -pref("dom.workers.enabled", true); -// The number of workers per domain allowed to run concurrently. -pref("dom.workers.maxPerDomain", 20); - // Whether window.performance is enabled pref("dom.enable_performance", true); diff --git a/testing/mochitest/specialpowers/content/specialpowers.js b/testing/mochitest/specialpowers/content/specialpowers.js index 277efdc9e842..5baaa0806f67 100644 --- a/testing/mochitest/specialpowers/content/specialpowers.js +++ b/testing/mochitest/specialpowers/content/specialpowers.js @@ -50,7 +50,6 @@ function SpecialPowers(window) { this._pongHandlers = []; this._messageListener = this._messageReceived.bind(this); addMessageListener("SPPingService", this._messageListener); - this._consoleListeners = []; } function bindDOMWindowUtils(sp, window) { @@ -191,32 +190,6 @@ SpecialPowers.prototype = { removeEventListener(type, listener, capture); }, - addErrorConsoleListener: function(listener) { - var consoleListener = { - userListener: listener, - observe: function(consoleMessage) { - this.userListener(consoleMessage.message); - } - }; - - Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService) - .registerListener(consoleListener); - - this._consoleListeners.push(consoleListener); - }, - - removeErrorConsoleListener: function(listener) { - for (var index in this._consoleListeners) { - var consoleListener = this._consoleListeners[index]; - if (consoleListener.userListener == listener) { - Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService) - .unregisterListener(consoleListener); - this._consoleListeners = this._consoleListeners.splice(index, 1); - break; - } - } - }, - getFullZoom: function(window) { return this._getMUDV(window).fullZoom; }, diff --git a/toolkit/components/console/hudservice/tests/browser/browser_webconsole_bug_595934_message_categories.js b/toolkit/components/console/hudservice/tests/browser/browser_webconsole_bug_595934_message_categories.js index 818a66a058b1..39c1fa2759fd 100644 --- a/toolkit/components/console/hudservice/tests/browser/browser_webconsole_bug_595934_message_categories.js +++ b/toolkit/components/console/hudservice/tests/browser/browser_webconsole_bug_595934_message_categories.js @@ -51,7 +51,7 @@ const TESTS = [ }, { // #7 file: "test-bug-595934-workers.html", - category: "Web Worker", + category: "DOM Worker javascript", matchString: "fooBarWorker", }, { // #8