From d8739dcb8ac578f87f09054bc879fdb6ac610913 Mon Sep 17 00:00:00 2001 From: Ben Turner Date: Tue, 21 Dec 2010 14:28:08 -0500 Subject: [PATCH] Bug 618484 - 'Allow ChromeWorkers access to XPCOM objects'. r=jst, a=blocking. --- dom/base/nsJSEnvironment.cpp | 6 +- dom/indexedDB/IDBObjectStore.cpp | 2 +- dom/src/threads/nsDOMThreadService.cpp | 40 ++ dom/src/threads/nsDOMThreadService.h | 16 + dom/src/threads/nsDOMWorker.cpp | 368 +++++++++++++++++- dom/src/threads/nsDOMWorker.h | 6 + dom/src/threads/nsDOMWorkerEvents.cpp | 22 +- dom/src/threads/nsDOMWorkerEvents.h | 4 +- .../threads/nsDOMWorkerSecurityManager.cpp | 20 +- dom/src/threads/test/Makefile.in | 2 + dom/src/threads/test/chromeWorker_worker.js | 34 +- dom/src/threads/test/test_chromeWorker.xul | 19 + .../test/test_chromeWorkerComponent.xul | 6 + dom/src/threads/test/test_xpcom.html | 45 +++ dom/src/threads/test/xpcom_worker.js | 18 + js/src/jsapi.cpp | 34 +- js/src/jsapi.h | 39 +- js/src/jsclone.cpp | 25 +- js/src/jsclone.h | 28 +- js/src/jspubtd.h | 10 +- js/src/shell/js.cpp | 4 +- js/src/shell/jsworkers.cpp | 5 +- 22 files changed, 686 insertions(+), 67 deletions(-) create mode 100644 dom/src/threads/test/test_xpcom.html create mode 100644 dom/src/threads/test/xpcom_worker.js diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index ae8c30f42dd..bae0183d843 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -4071,7 +4071,8 @@ static JSObject* DOMReadStructuredClone(JSContext* cx, JSStructuredCloneReader* reader, uint32 tag, - uint32 data) + uint32 data, + void* closure) { // We don't currently support any extensions to structured cloning. nsDOMClassInfo::ThrowJSException(cx, NS_ERROR_DOM_DATA_CLONE_ERR); @@ -4081,7 +4082,8 @@ DOMReadStructuredClone(JSContext* cx, static JSBool DOMWriteStructuredClone(JSContext* cx, JSStructuredCloneWriter* writer, - JSObject* obj) + JSObject* obj, + void *closure) { // We don't currently support any extensions to structured cloning. nsDOMClassInfo::ThrowJSException(cx, NS_ERROR_DOM_DATA_CLONE_ERR); diff --git a/dom/indexedDB/IDBObjectStore.cpp b/dom/indexedDB/IDBObjectStore.cpp index 1b7be7fdd65..7388eaae737 100644 --- a/dom/indexedDB/IDBObjectStore.cpp +++ b/dom/indexedDB/IDBObjectStore.cpp @@ -528,7 +528,7 @@ IDBObjectStore::GetKeyPathValueFromStructuredData(const PRUint8* aData, jsval clone; if (!JS_ReadStructuredClone(cx, reinterpret_cast(aData), aDataLength, JS_STRUCTURED_CLONE_VERSION, - &clone)) { + &clone, NULL, NULL)) { return NS_ERROR_DOM_DATA_CLONE_ERR; } diff --git a/dom/src/threads/nsDOMThreadService.cpp b/dom/src/threads/nsDOMThreadService.cpp index 60f4ed4afad..8aca51b80b1 100644 --- a/dom/src/threads/nsDOMThreadService.cpp +++ b/dom/src/threads/nsDOMThreadService.cpp @@ -755,6 +755,9 @@ nsDOMThreadService::Init() 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); @@ -1214,6 +1217,43 @@ nsDOMThreadService::ChangeThreadPoolMaxThreads(PRInt16 aDelta) return NS_OK; } +void +nsDOMThreadService::NoteThreadsafeContractId(const nsACString& aContractId, + PRBool aIsThreadsafe) +{ + NS_ASSERTION(!aContractId.IsEmpty(), "Empty contract id!"); + + nsAutoMonitor mon(mMonitor); + +#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!"); + + nsAutoMonitor mon(mMonitor); + + PRBool isThreadsafe; + if (mThreadsafeContractIDs.Get(aContractId, &isThreadsafe)) { + return isThreadsafe ? Threadsafe : NotThreadsafe; + } + + return Unknown; +} + // static nsIJSRuntimeService* nsDOMThreadService::JSRuntimeService() diff --git a/dom/src/threads/nsDOMThreadService.h b/dom/src/threads/nsDOMThreadService.h index 997daa2a93d..d4dadd1643e 100644 --- a/dom/src/threads/nsDOMThreadService.h +++ b/dom/src/threads/nsDOMThreadService.h @@ -49,6 +49,7 @@ #include "jsapi.h" #include "nsAutoPtr.h" #include "nsCOMPtr.h" +#include "nsDataHashtable.h" #include "nsRefPtrHashtable.h" #include "nsStringGlue.h" #include "nsTPtrArray.h" @@ -69,6 +70,13 @@ class nsIThreadJSContextStack; class nsIXPConnect; class nsIXPCSecurityManager; +enum ThreadsafeStatus +{ + Threadsafe, + NotThreadsafe, + Unknown +}; + class nsDOMThreadService : public nsIEventTarget, public nsIObserver, public nsIThreadPoolListener @@ -114,6 +122,11 @@ public: nsresult ChangeThreadPoolMaxThreads(PRInt16 aDelta); + void NoteThreadsafeContractId(const nsACString& aContractId, + PRBool aIsThreadsafe); + + ThreadsafeStatus GetContractIdThreadsafeStatus(const nsACString& aContractId); + private: nsDOMThreadService(); ~nsDOMThreadService(); @@ -185,6 +198,9 @@ private: // suspended. Always protected with mMonitor. nsTArray mSuspendedWorkers; + // Always protected with mMonitor. + nsDataHashtable mThreadsafeContractIDs; + nsString mAppName; nsString mAppVersion; nsString mPlatform; diff --git a/dom/src/threads/nsDOMWorker.cpp b/dom/src/threads/nsDOMWorker.cpp index 5ddf57e60ae..ff46ba34fcc 100644 --- a/dom/src/threads/nsDOMWorker.cpp +++ b/dom/src/threads/nsDOMWorker.cpp @@ -51,6 +51,7 @@ #include "nsAutoLock.h" #include "nsAXPCNativeCallContext.h" #include "nsContentUtils.h" +#include "nsDOMClassInfo.h" #include "nsDOMClassInfoID.h" #include "nsGlobalWindow.h" #include "nsJSON.h" @@ -69,6 +70,57 @@ #include "nsDOMWorkerTimeout.h" #include "nsDOMWorkerXHR.h" +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: @@ -115,6 +167,19 @@ public: 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); @@ -128,6 +193,15 @@ private: 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 @@ -408,6 +482,170 @@ nsDOMWorkerFunctions::NewChromeWorker(JSContext* aCx, 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!"); + JSString* str = JSID_TO_STRING(aId); + NS_ASSERTION(nsDependentJSString(str).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, @@ -549,6 +787,70 @@ JSFunctionSpec gDOMWorkerChromeFunctions[] = { { 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 + 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), @@ -1656,11 +1958,19 @@ nsDOMWorker::PostMessageInternal(PRBool aToInner) 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; - - if (!buffer.write(cx, argv[0])) { + nsTArray > wrappedNatives; + if (!buffer.write(cx, argv[0], &callbacks, &wrappedNatives)) { return NS_ERROR_DOM_DATA_CLONE_ERR; } @@ -1672,7 +1982,7 @@ nsDOMWorker::PostMessageInternal(PRBool aToInner) nsnull); NS_ENSURE_SUCCESS(rv, rv); - rv = message->SetJSData(cx, buffer); + rv = message->SetJSData(cx, buffer, wrappedNatives); NS_ENSURE_SUCCESS(rv, rv); nsRefPtr runnable = @@ -1796,6 +2106,11 @@ nsDOMWorker::CompileGlobalObject(JSContext* aCx, nsLazyAutoRequest *aRequest, 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, @@ -2192,6 +2507,53 @@ nsDOMWorker::GetExpirationTime() } #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) { diff --git a/dom/src/threads/nsDOMWorker.h b/dom/src/threads/nsDOMWorker.h index 901eb5126af..a22c34a22fc 100644 --- a/dom/src/threads/nsDOMWorker.h +++ b/dom/src/threads/nsDOMWorker.h @@ -234,6 +234,12 @@ public: 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. diff --git a/dom/src/threads/nsDOMWorkerEvents.cpp b/dom/src/threads/nsDOMWorkerEvents.cpp index 7b32eb511c4..e788939d0e1 100644 --- a/dom/src/threads/nsDOMWorkerEvents.cpp +++ b/dom/src/threads/nsDOMWorkerEvents.cpp @@ -279,8 +279,10 @@ NS_IMPL_CI_INTERFACE_GETTER2(nsDOMWorkerMessageEvent, nsIDOMEvent, NS_IMPL_THREADSAFE_DOM_CI_GETINTERFACES(nsDOMWorkerMessageEvent) nsresult -nsDOMWorkerMessageEvent::SetJSData(JSContext* aCx, - JSAutoStructuredCloneBuffer& aBuffer) +nsDOMWorkerMessageEvent::SetJSData( + JSContext* aCx, + JSAutoStructuredCloneBuffer& aBuffer, + nsTArray >& aWrappedNatives) { NS_ASSERTION(aCx, "Null context!"); @@ -289,6 +291,10 @@ nsDOMWorkerMessageEvent::SetJSData(JSContext* aCx, return NS_ERROR_FAILURE; } + if (!mWrappedNatives.SwapElements(aWrappedNatives)) { + NS_ERROR("This should never fail!"); + } + aBuffer.steal(&mData, &mDataLen); return NS_OK; } @@ -315,7 +321,17 @@ nsDOMWorkerMessageEvent::GetData(nsAString& aData) mData = nsnull; mDataLen = 0; - if (!buffer.read(mDataVal.ToJSValPtr())) { + 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; } diff --git a/dom/src/threads/nsDOMWorkerEvents.h b/dom/src/threads/nsDOMWorkerEvents.h index a8856720ff1..602d5942831 100644 --- a/dom/src/threads/nsDOMWorkerEvents.h +++ b/dom/src/threads/nsDOMWorkerEvents.h @@ -215,7 +215,8 @@ public: ~nsDOMWorkerMessageEvent(); nsresult SetJSData(JSContext* aCx, - JSAutoStructuredCloneBuffer& aBuffer); + JSAutoStructuredCloneBuffer& aBuffer, + nsTArray >& aWrappedNatives); protected: nsString mOrigin; @@ -224,6 +225,7 @@ protected: nsAutoJSValHolder mDataVal; uint64* mData; size_t mDataLen; + nsTArray > mWrappedNatives; }; class nsDOMWorkerProgressEvent : public nsDOMWorkerEvent, diff --git a/dom/src/threads/nsDOMWorkerSecurityManager.cpp b/dom/src/threads/nsDOMWorkerSecurityManager.cpp index fe1b82a3e4a..5af623556b8 100644 --- a/dom/src/threads/nsDOMWorkerSecurityManager.cpp +++ b/dom/src/threads/nsDOMWorkerSecurityManager.cpp @@ -43,9 +43,12 @@ // 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) @@ -73,7 +76,7 @@ NS_IMPL_THREADSAFE_ISUPPORTS1(nsDOMWorkerSecurityManager, nsIXPCSecurityManager) NS_IMETHODIMP -nsDOMWorkerSecurityManager::CanCreateWrapper(JSContext* aJSContext, +nsDOMWorkerSecurityManager::CanCreateWrapper(JSContext* aCx, const nsIID& aIID, nsISupports* aObj, nsIClassInfo* aClassInfo, @@ -83,19 +86,22 @@ nsDOMWorkerSecurityManager::CanCreateWrapper(JSContext* aJSContext, } NS_IMETHODIMP -nsDOMWorkerSecurityManager::CanCreateInstance(JSContext* aJSContext, +nsDOMWorkerSecurityManager::CanCreateInstance(JSContext* aCx, const nsCID& aCID) { - NS_NOTREACHED("Should not call this!"); - return NS_ERROR_UNEXPECTED; + return CanGetService(aCx, aCID); } NS_IMETHODIMP -nsDOMWorkerSecurityManager::CanGetService(JSContext* aJSContext, +nsDOMWorkerSecurityManager::CanGetService(JSContext* aCx, const nsCID& aCID) { - NS_NOTREACHED("Should not call this!"); - return NS_ERROR_UNEXPECTED; + 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 diff --git a/dom/src/threads/test/Makefile.in b/dom/src/threads/test/Makefile.in index 4dd0e30fb28..8ec56b5b026 100644 --- a/dom/src/threads/test/Makefile.in +++ b/dom/src/threads/test/Makefile.in @@ -119,6 +119,8 @@ _TEST_FILES = \ newError_worker.js \ test_chromeWorker.html \ WorkerTest_badworker.js \ + test_xpcom.html \ + xpcom_worker.js \ $(NULL) _SUBDIR_TEST_FILES = \ diff --git a/dom/src/threads/test/chromeWorker_worker.js b/dom/src/threads/test/chromeWorker_worker.js index 255552eed88..fb7c2d04b6e 100644 --- a/dom/src/threads/test/chromeWorker_worker.js +++ b/dom/src/threads/test/chromeWorker_worker.js @@ -35,8 +35,34 @@ * * ***** END LICENSE BLOCK ***** */ -let worker = new ChromeWorker("chromeWorker_subworker.js"); -worker.onmessage = function(event) { - postMessage(event.data); +// 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!"; +} + +// Test XPCOM.createInstance +let threadPool = XPCOM.createInstance("@mozilla.org/thread-pool;1"); +threadPool.shutdown(); + +let notThreadsafe; +try { + notThreadsafe = XPCOM.createInstance("@mozilla.org/supports-PRBool;1"); +} +catch(e) { } + +if (notThreadsafe) { + throw "Shouldn't be able to create non-threadsafe component!"; +} + +function onmessage(event) { + // Test passing wrapped natives from the main thread. + event.data.shutdown(); + + let worker = new ChromeWorker("chromeWorker_subworker.js"); + worker.onmessage = function(event) { + postMessage(event.data); + } + worker.postMessage("Go"); } -worker.postMessage("Go"); diff --git a/dom/src/threads/test/test_chromeWorker.xul b/dom/src/threads/test/test_chromeWorker.xul index ae0afcf302d..071d742dbda 100644 --- a/dom/src/threads/test/test_chromeWorker.xul +++ b/dom/src/threads/test/test_chromeWorker.xul @@ -64,6 +64,25 @@ worker.terminate(); SimpleTest.finish(); } + + // Test passing a non-threadsafe wrapped native to the worker. + var isupports = + Components.classes["@mozilla.org/supports-PRBool;1"] + .createInstance(Components.interfaces.nsISupportsPRBool); + + try { + worker.postMessage(isupports); + ok(false, "Passing non-threadsafe thing should throw!"); + } + catch (e) { + ok(true, "Passing non-threadsafe thing threw"); + } + + // Test passing a wrapped native to the worker. + var thread = Components.classes["@mozilla.org/thread-manager;1"] + .getService().newThread(0); + + worker.postMessage(thread); } ]]> diff --git a/dom/src/threads/test/test_chromeWorkerComponent.xul b/dom/src/threads/test/test_chromeWorkerComponent.xul index c7b15e31d97..ad8f2eabdf2 100644 --- a/dom/src/threads/test/test_chromeWorkerComponent.xul +++ b/dom/src/threads/test/test_chromeWorkerComponent.xul @@ -68,6 +68,12 @@ worker.terminate(); SimpleTest.finish(); } + + // Test passing a wrapped native to the worker. + var thread = Components.classes["@mozilla.org/thread-manager;1"] + .getService().newThread(0); + + worker.postMessage(thread); } ]]> diff --git a/dom/src/threads/test/test_xpcom.html b/dom/src/threads/test/test_xpcom.html new file mode 100644 index 00000000000..3962a9bb3b9 --- /dev/null +++ b/dom/src/threads/test/test_xpcom.html @@ -0,0 +1,45 @@ + + + + Test for DOM Worker Threads + + + + + +
+
+
+ + + diff --git a/dom/src/threads/test/xpcom_worker.js b/dom/src/threads/test/xpcom_worker.js new file mode 100644 index 00000000000..79fcca6000e --- /dev/null +++ b/dom/src/threads/test/xpcom_worker.js @@ -0,0 +1,18 @@ +var exception; +try { + var xpcom = XPCOM; +} +catch(e) { + exception = e; +} + +if (!exception) { + throw "Worker shouldn't be able to access the XPCOM object!"; +} + +onmessage = function(event) { + if (event.data != "Hi") { + throw "Bad message!"; + } + postMessage("Done"); +} diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 730a3411d11..6ac755be22d 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -5481,26 +5481,48 @@ JS_FinishJSONParse(JSContext *cx, JSONParser *jp, jsval reviver) } JS_PUBLIC_API(JSBool) -JS_ReadStructuredClone(JSContext *cx, const uint64 *buf, size_t nbytes, uint32 version, jsval *vp) +JS_ReadStructuredClone(JSContext *cx, const uint64 *buf, size_t nbytes, + uint32 version, jsval *vp, + const JSStructuredCloneCallbacks *optionalCallbacks, + void *closure) { if (version > JS_STRUCTURED_CLONE_VERSION) { JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_CLONE_VERSION); return false; } - return ReadStructuredClone(cx, buf, nbytes, Valueify(vp)); + const JSStructuredCloneCallbacks *callbacks = + optionalCallbacks ? + optionalCallbacks : + cx->runtime->structuredCloneCallbacks; + return ReadStructuredClone(cx, buf, nbytes, Valueify(vp), callbacks, closure); } JS_PUBLIC_API(JSBool) -JS_WriteStructuredClone(JSContext *cx, jsval v, uint64 **bufp, size_t *nbytesp) +JS_WriteStructuredClone(JSContext *cx, jsval v, uint64 **bufp, size_t *nbytesp, + const JSStructuredCloneCallbacks *optionalCallbacks, + void *closure) { - return WriteStructuredClone(cx, Valueify(v), (uint64_t **) bufp, nbytesp); + const JSStructuredCloneCallbacks *callbacks = + optionalCallbacks ? + optionalCallbacks : + cx->runtime->structuredCloneCallbacks; + return WriteStructuredClone(cx, Valueify(v), (uint64_t **) bufp, nbytesp, + callbacks, closure); } JS_PUBLIC_API(JSBool) -JS_StructuredClone(JSContext *cx, jsval v, jsval *vp) +JS_StructuredClone(JSContext *cx, jsval v, jsval *vp, + ReadStructuredCloneOp optionalReadOp, + const JSStructuredCloneCallbacks *optionalCallbacks, + void *closure) { + const JSStructuredCloneCallbacks *callbacks = + optionalCallbacks ? + optionalCallbacks : + cx->runtime->structuredCloneCallbacks; JSAutoStructuredCloneBuffer buf; - return buf.write(cx, v) && buf.read(vp); + return buf.write(cx, v, callbacks, closure) && + buf.read(vp, cx, callbacks, closure); } JS_PUBLIC_API(void) diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 4a5b76c020f..013da362bc2 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -3207,15 +3207,28 @@ JS_FinishJSONParse(JSContext *cx, JSONParser *jp, jsval reviver); /* The maximum supported structured-clone serialization format version. */ #define JS_STRUCTURED_CLONE_VERSION 1 +struct JSStructuredCloneCallbacks { + ReadStructuredCloneOp read; + WriteStructuredCloneOp write; + StructuredCloneErrorOp reportError; +}; + JS_PUBLIC_API(JSBool) -JS_ReadStructuredClone(JSContext *cx, const uint64 *data, size_t nbytes, uint32 version, jsval *vp); +JS_ReadStructuredClone(JSContext *cx, const uint64 *data, size_t nbytes, + uint32 version, jsval *vp, + const JSStructuredCloneCallbacks *optionalCallbacks, + void *closure); /* Note: On success, the caller is responsible for calling js_free(*datap). */ JS_PUBLIC_API(JSBool) -JS_WriteStructuredClone(JSContext *cx, jsval v, uint64 **datap, size_t *nbytesp); +JS_WriteStructuredClone(JSContext *cx, jsval v, uint64 **datap, size_t *nbytesp, + const JSStructuredCloneCallbacks *optionalCallbacks, + void *closure); JS_PUBLIC_API(JSBool) -JS_StructuredClone(JSContext *cx, jsval v, jsval *vp); +JS_StructuredClone(JSContext *cx, jsval v, jsval *vp, + const JSStructuredCloneCallbacks *optionalCallbacks, + void *closure); #ifdef __cplusplus /* RAII sugar for JS_WriteStructuredClone. */ @@ -3280,18 +3293,24 @@ class JSAutoStructuredCloneBuffer { version_ = 0; } - bool read(jsval *vp, JSContext *cx=NULL) const { + bool read(jsval *vp, JSContext *cx=NULL, + const JSStructuredCloneCallbacks *optionalCallbacks=NULL, + void *closure=NULL) const { if (!cx) cx = cx_; JS_ASSERT(cx); JS_ASSERT(data_); - return !!JS_ReadStructuredClone(cx, data_, nbytes_, version_, vp); + return !!JS_ReadStructuredClone(cx, data_, nbytes_, version_, vp, + optionalCallbacks, closure); } - bool write(JSContext *cx, jsval v) { + bool write(JSContext *cx, jsval v, + const JSStructuredCloneCallbacks *optionalCallbacks=NULL, + void *closure=NULL) { clear(cx); cx_ = cx; - bool ok = !!JS_WriteStructuredClone(cx, v, &data_, &nbytes_); + bool ok = !!JS_WriteStructuredClone(cx, v, &data_, &nbytes_, + optionalCallbacks, closure); if (!ok) { data_ = NULL; nbytes_ = 0; @@ -3335,12 +3354,6 @@ class JSAutoStructuredCloneBuffer { #define JS_SCERR_RECURSION 0 -struct JSStructuredCloneCallbacks { - ReadStructuredCloneOp read; - WriteStructuredCloneOp write; - StructuredCloneErrorOp reportError; -}; - JS_PUBLIC_API(void) JS_SetStructuredCloneCallbacks(JSRuntime *rt, const JSStructuredCloneCallbacks *callbacks); diff --git a/js/src/jsclone.cpp b/js/src/jsclone.cpp index f49bf9cb0ae..2972939353c 100644 --- a/js/src/jsclone.cpp +++ b/js/src/jsclone.cpp @@ -49,18 +49,20 @@ namespace js { bool -WriteStructuredClone(JSContext *cx, const Value &v, uint64 **bufp, size_t *nbytesp) +WriteStructuredClone(JSContext *cx, const Value &v, uint64 **bufp, size_t *nbytesp, + const JSStructuredCloneCallbacks *cb, void *cbClosure) { SCOutput out(cx); - JSStructuredCloneWriter w(out); + JSStructuredCloneWriter w(out, cb, cbClosure); return w.init() && w.write(v) && out.extractBuffer(bufp, nbytesp); } bool -ReadStructuredClone(JSContext *cx, const uint64_t *data, size_t nbytes, Value *vp) +ReadStructuredClone(JSContext *cx, const uint64_t *data, size_t nbytes, Value *vp, + const JSStructuredCloneCallbacks *cb, void *cbClosure) { SCInput in(cx, data, nbytes); - JSStructuredCloneReader r(in); + JSStructuredCloneReader r(in, cb, cbClosure); return r.read(vp); } @@ -465,9 +467,8 @@ JSStructuredCloneWriter::startObject(JSObject *obj) HashSet::AddPtr p = memory.lookupForAdd(obj); if (p) { JSContext *cx = context(); - const JSStructuredCloneCallbacks *cb = cx->runtime->structuredCloneCallbacks; - if (cb) - cb->reportError(cx, JS_SCERR_RECURSION); + if (callbacks && callbacks->reportError) + callbacks->reportError(cx, JS_SCERR_RECURSION); else JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_SC_RECURSION); return false; @@ -532,9 +533,8 @@ JSStructuredCloneWriter::startWrite(const js::Value &v) return writeString(SCTAG_STRING_OBJECT, obj->getPrimitiveThis().toString()); } - const JSStructuredCloneCallbacks *cb = context()->runtime->structuredCloneCallbacks; - if (cb) - return cb->write(context(), this, obj); + if (callbacks && callbacks->write) + return callbacks->write(context(), this, obj, closure); /* else fall through */ } @@ -786,13 +786,12 @@ JSStructuredCloneReader::startRead(Value *vp) if (SCTAG_TYPED_ARRAY_MIN <= tag && tag <= SCTAG_TYPED_ARRAY_MAX) return readTypedArray(tag, data, vp); - const JSStructuredCloneCallbacks *cb = context()->runtime->structuredCloneCallbacks; - if (!cb) { + if (!callbacks || !callbacks->read) { JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA, "unsupported type"); return false; } - JSObject *obj = cb->read(context(), this, tag, data); + JSObject *obj = callbacks->read(context(), this, tag, data, closure); if (!obj) return false; vp->setObject(*obj); diff --git a/js/src/jsclone.h b/js/src/jsclone.h index 51fde875b6b..dbead173c81 100644 --- a/js/src/jsclone.h +++ b/js/src/jsclone.h @@ -49,10 +49,12 @@ namespace js { bool -WriteStructuredClone(JSContext *cx, const Value &v, uint64_t **bufp, size_t *nbytesp); +WriteStructuredClone(JSContext *cx, const Value &v, uint64_t **bufp, size_t *nbytesp, + const JSStructuredCloneCallbacks *cb, void *cbClosure); bool -ReadStructuredClone(JSContext *cx, const uint64_t *data, size_t nbytes, Value *vp); +ReadStructuredClone(JSContext *cx, const uint64_t *data, size_t nbytes, Value *vp, + const JSStructuredCloneCallbacks *cb, void *cbClosure); struct SCOutput { public: @@ -109,8 +111,9 @@ struct SCInput { struct JSStructuredCloneReader { public: - explicit JSStructuredCloneReader(js::SCInput &in) - : in(in), objs(in.context()) {} + explicit JSStructuredCloneReader(js::SCInput &in, const JSStructuredCloneCallbacks *cb, + void *cbClosure) + : in(in), objs(in.context()), callbacks(cb), closure(cbClosure) { } js::SCInput &input() { return in; } bool read(js::Value *vp); @@ -129,13 +132,20 @@ struct JSStructuredCloneReader { // Stack of objects with properties remaining to be read. js::AutoValueVector objs; + + // The user defined callbacks that will be used for cloning. + const JSStructuredCloneCallbacks *callbacks; + + // Any value passed to JS_ReadStructuredClone. + void *closure; }; struct JSStructuredCloneWriter { public: - explicit JSStructuredCloneWriter(js::SCOutput &out) + explicit JSStructuredCloneWriter(js::SCOutput &out, const JSStructuredCloneCallbacks *cb, + void *cbClosure) : out(out), objs(out.context()), counts(out.context()), ids(out.context()), - memory(out.context()) {} + memory(out.context()), callbacks(cb), closure(cbClosure) { } bool init() { return memory.init(); } @@ -170,6 +180,12 @@ struct JSStructuredCloneWriter { // The "memory" list described in the HTML5 internal structured cloning algorithm. // memory has the same elements as objs. js::HashSet memory; + + // The user defined callbacks that will be used for cloning. + const JSStructuredCloneCallbacks *callbacks; + + // Any value passed to JS_WriteStructuredClone. + void *closure; }; #endif /* jsclone_h___ */ diff --git a/js/src/jspubtd.h b/js/src/jspubtd.h index 4c7cfbc0559..c20847a80fb 100644 --- a/js/src/jspubtd.h +++ b/js/src/jspubtd.h @@ -584,10 +584,11 @@ typedef JSBool * * tag and data are the pair of uint32 values from the header. The callback may * use the JS_Read* APIs to read any other relevant parts of the object from - * the reader r. Return the new object on success, NULL on error/exception. + * the reader r. closure is any value passed to the JS_ReadStructuredClone + * function. Return the new object on success, NULL on error/exception. */ typedef JSObject *(*ReadStructuredCloneOp)(JSContext *cx, JSStructuredCloneReader *r, - uint32 tag, uint32 data); + uint32 tag, uint32 data, void *closure); /* * Structured data serialization hook. The engine can write primitive values, @@ -596,11 +597,12 @@ typedef JSObject *(*ReadStructuredCloneOp)(JSContext *cx, JSStructuredCloneReade * the JS_WriteUint32Pair API to write an object header, passing a value * greater than JS_SCTAG_USER to the tag parameter. Then it can use the * JS_Write* APIs to write any other relevant parts of the value v to the - * writer w. + * writer w. closure is any value passed to the JS_WriteStructuredCLone function. * * Return true on success, false on error/exception. */ -typedef JSBool (*WriteStructuredCloneOp)(JSContext *cx, JSStructuredCloneWriter *w, JSObject *obj); +typedef JSBool (*WriteStructuredCloneOp)(JSContext *cx, JSStructuredCloneWriter *w, + JSObject *obj, void *closure); /* * This is called when JS_WriteStructuredClone finds that the object to be diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index f2ff74c2eec..b3dfd2d7c34 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -4169,7 +4169,7 @@ Serialize(JSContext *cx, uintN argc, jsval *vp) jsval v = argc > 0 ? JS_ARGV(cx, vp)[0] : JSVAL_VOID; uint64 *datap; size_t nbytes; - if (!JS_WriteStructuredClone(cx, v, &datap, &nbytes)) + if (!JS_WriteStructuredClone(cx, v, &datap, &nbytes, NULL, NULL)) return false; JSObject *arrayobj = js_CreateTypedArray(cx, TypedArray::TYPE_UINT8, nbytes); @@ -4201,7 +4201,7 @@ Deserialize(JSContext *cx, uintN argc, jsval *vp) } if (!JS_ReadStructuredClone(cx, (uint64 *) array->data, array->byteLength, - JS_STRUCTURED_CLONE_VERSION, &v)) { + JS_STRUCTURED_CLONE_VERSION, &v, NULL, NULL)) { return false; } JS_SET_RVAL(cx, vp, v); diff --git a/js/src/shell/jsworkers.cpp b/js/src/shell/jsworkers.cpp index 9824849597e..139d19e84ad 100644 --- a/js/src/shell/jsworkers.cpp +++ b/js/src/shell/jsworkers.cpp @@ -294,7 +294,8 @@ class Event } bool deserializeData(JSContext *cx, jsval *vp) { - return !!JS_ReadStructuredClone(cx, data, nbytes, JS_STRUCTURED_CLONE_VERSION, vp); + return !!JS_ReadStructuredClone(cx, data, nbytes, JS_STRUCTURED_CLONE_VERSION, vp, + NULL, NULL); } virtual Result process(JSContext *cx) = 0; @@ -307,7 +308,7 @@ class Event { uint64 *data; size_t nbytes; - if (!JS_WriteStructuredClone(cx, v, &data, &nbytes)) + if (!JS_WriteStructuredClone(cx, v, &data, &nbytes, NULL, NULL)) return NULL; EventType *event = new EventType;