Bug 618484 - 'Allow ChromeWorkers access to XPCOM objects'. r=jst, a=blocking.

This commit is contained in:
Ben Turner 2010-12-21 14:28:08 -05:00
parent 173d24bc86
commit d8739dcb8a
22 changed files with 686 additions and 67 deletions

View File

@ -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);

View File

@ -528,7 +528,7 @@ IDBObjectStore::GetKeyPathValueFromStructuredData(const PRUint8* aData,
jsval clone;
if (!JS_ReadStructuredClone(cx, reinterpret_cast<const uint64*>(aData),
aDataLength, JS_STRUCTURED_CLONE_VERSION,
&clone)) {
&clone, NULL, NULL)) {
return NS_ERROR_DOM_DATA_CLONE_ERR;
}

View File

@ -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()

View File

@ -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<nsDOMWorkerRunnable*> mSuspendedWorkers;
// Always protected with mMonitor.
nsDataHashtable<nsCStringHashKey, PRBool> mThreadsafeContractIDs;
nsString mAppName;
nsString mAppVersion;
nsString mPlatform;

View File

@ -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<nsISupports> instance;
if (mService) {
instance = do_GetService(mContractId.get(), &rv);
}
else {
instance = do_CreateInstance(mContractId.get(), &rv);
}
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIClassInfo> 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<nsDOMWorker*>(JS_GetContextPrivate(aCx));
NS_ASSERTION(worker, "This should be set by the DOM thread service!");
if (worker->IsCanceled()) {
return JS_FALSE;
}
PRUint16 dummy;
nsCOMPtr<nsIXPCSecurityManager> 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<nsDOMWorker*>(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<nsIThread> mainThread;
nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread));
if (NS_FAILED(rv)) {
JS_ReportError(aCx, "Failed to get main thread!");
return JS_FALSE;
}
nsRefPtr<TestComponentThreadsafetyRunnable> 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<nsISupports> 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<nsIXPConnectJSObjectHolder> 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<nsCOMPtr<nsISupports> >* wrappedNatives =
static_cast<nsTArray<nsCOMPtr<nsISupports> >*>(aClosure);
// See if this is a wrapped native.
nsCOMPtr<nsIXPConnectWrappedNative> 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<nsIClassInfo> 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<nsCOMPtr<nsISupports> > 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<nsDOMFireEventRunnable> 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<nsIXPConnectJSObjectHolder> 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)
{

View File

@ -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.

View File

@ -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<nsCOMPtr<nsISupports> >& 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;
}

View File

@ -215,7 +215,8 @@ public:
~nsDOMWorkerMessageEvent();
nsresult SetJSData(JSContext* aCx,
JSAutoStructuredCloneBuffer& aBuffer);
JSAutoStructuredCloneBuffer& aBuffer,
nsTArray<nsCOMPtr<nsISupports> >& aWrappedNatives);
protected:
nsString mOrigin;
@ -224,6 +225,7 @@ protected:
nsAutoJSValHolder mDataVal;
uint64* mData;
size_t mDataLen;
nsTArray<nsCOMPtr<nsISupports> > mWrappedNatives;
};
class nsDOMWorkerProgressEvent : public nsDOMWorkerEvent,

View File

@ -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<nsDOMWorker*>(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

View File

@ -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 = \

View File

@ -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");

View File

@ -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);
}
]]>

View File

@ -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);
}
]]>

View File

@ -0,0 +1,45 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for DOM Worker Threads</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
var worker = new Worker("xpcom_worker.js");
worker.onmessage = function(event) {
is(event.data, "Done", "Correct message");
SimpleTest.finish();
};
worker.onerror = function(event) {
ok(false, "Worker had an error: " + event.message);
SimpleTest.finish();
};
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
// Test passing a wrapped native to the worker.
var thread = Components.classes["@mozilla.org/thread-manager;1"]
.getService().newThread(0);
try {
worker.postMessage(thread);
ok(false, "postMessage with a wrapped native should fail!");
}
catch(e) {
ok(true, "postMessage with a wrapped native failed");
}
worker.postMessage("Hi");
SimpleTest.waitForExplicitFinish();
</script>
</pre>
</body>
</html>

View File

@ -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");
}

View File

@ -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)

View File

@ -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);

View File

@ -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<JSObject *>::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);

View File

@ -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<JSObject *> 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___ */

View File

@ -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

View File

@ -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);

View File

@ -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;