Bug 453865 - 'Workers: Allow JSON-able objects to be passed as messages to worker threads.' r+sr+a=jst.

This commit is contained in:
Ben Turner 2008-12-07 16:15:49 -08:00
parent 9533cd1dca
commit a3f1dfd7b3
12 changed files with 498 additions and 23 deletions

View File

@ -45,10 +45,10 @@
interface nsIDOMEventListener;
[scriptable, uuid(6c32d0c5-6bfa-438b-ad44-be0df80cd4a8)]
[scriptable, uuid(ab3725b8-3fca-40cc-a42c-92fb154ef01d)]
interface nsIWorkerMessagePort : nsISupports
{
void postMessage(in DOMString aMessage);
void postMessage(/* in JSObject aMessage */);
};
[scriptable, uuid(508f2d49-e9a0-4fe8-bd33-321820173b4a)]
@ -83,11 +83,10 @@ interface nsIWorkerGlobalScope : nsISupports
readonly attribute nsIWorkerNavigator navigator;
};
[scriptable, uuid(b10cfe72-91b9-45c6-ab13-33f89c2d0e56)]
[scriptable, uuid(d30a2f61-86e2-434e-837f-4f1985efa865)]
interface nsIWorkerScope : nsIWorkerGlobalScope
{
void postMessage(in DOMString aMessage,
[optional] in nsIWorkerMessagePort aMessagePort);
void postMessage(/* in JSObject aMessage */);
attribute nsIDOMEventListener onmessage;
};
@ -98,11 +97,10 @@ interface nsIAbstractWorker : nsIDOMEventTarget
attribute nsIDOMEventListener onerror;
};
[scriptable, uuid(3d2ca558-31f7-4893-aa6d-1db9a3cb5bb9)]
[scriptable, uuid(daf945c3-8d29-4724-8939-dd383f7d27a7)]
interface nsIWorker : nsIAbstractWorker
{
void postMessage(in DOMString aMessage,
[optional] in nsIWorkerMessagePort aMessagePort);
void postMessage(/* in JSObject aMessage */);
attribute nsIDOMEventListener onmessage;

View File

@ -61,6 +61,7 @@ REQUIRES = \
pref \
string \
thebes \
uconv \
widget \
xpcom \
xpconnect \
@ -82,6 +83,7 @@ CPPSRCS = \
LOCAL_INCLUDES = \
-I$(topsrcdir)/dom/src/base \
-I$(topsrcdir)/dom/src/json \
-I$(topsrcdir)/content/base/src \
-I$(topsrcdir)/content/events/src \
$(NULL)

View File

@ -47,9 +47,11 @@
#include "jsdbgapi.h"
#endif
#include "nsAutoLock.h"
#include "nsAXPCNativeCallContext.h"
#include "nsContentUtils.h"
#include "nsDOMClassInfoID.h"
#include "nsGlobalWindow.h"
#include "nsJSON.h"
#include "nsJSUtils.h"
#include "nsProxyRelease.h"
#include "nsThreadUtils.h"
@ -413,6 +415,113 @@ JSFunctionSpec gDOMWorkerFunctions[] = {
{ nsnull, nsnull, 0, 0, 0 }
};
static JSBool
WriteCallback(const jschar* aBuffer,
uint32 aLength,
void* aData)
{
nsJSONWriter* writer = static_cast<nsJSONWriter*>(aData);
nsresult rv = writer->Write((const PRUnichar*)aBuffer, (PRUint32)aLength);
return NS_SUCCEEDED(rv) ? JS_TRUE : JS_FALSE;
}
static nsresult
GetStringForArgument(nsAString& aString,
PRBool* aIsJSON,
PRBool* aIsPrimitive)
{
NS_ASSERTION(aIsJSON && aIsPrimitive, "Null pointer!");
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);
JSAutoRequest ar(cx);
if (JSVAL_IS_STRING(argv[0])) {
aString.Assign(nsDependentJSString(JSVAL_TO_STRING(argv[0])));
*aIsJSON = *aIsPrimitive = PR_FALSE;
return NS_OK;
}
nsAutoJSValHolder jsonVal;
JSBool ok = jsonVal.Hold(cx);
NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
if (JSVAL_IS_PRIMITIVE(argv[0])) {
// Only objects can be serialized through JSON, currently, so if we've been
// given a primitive we set it as a property on a dummy object before
// sending it to the serializer.
JSObject* obj = JS_NewObject(cx, NULL, NULL, NULL);
NS_ENSURE_TRUE(obj, NS_ERROR_OUT_OF_MEMORY);
jsonVal = obj;
ok = JS_DefineProperty(cx, obj, JSON_PRIMITIVE_PROPNAME, argv[0], NULL,
NULL, JSPROP_ENUMERATE);
NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
*aIsPrimitive = PR_TRUE;
}
else {
jsonVal = argv[0];
*aIsPrimitive = PR_FALSE;
}
JSType type;
jsval* vp = jsonVal.ToJSValPtr();
// This may change vp if there is a 'toJSON' function on the object.
ok = JS_TryJSON(cx, vp);
if (!(ok && !JSVAL_IS_PRIMITIVE(*vp) &&
(type = JS_TypeOfValue(cx, *vp)) != JSTYPE_FUNCTION &&
type != JSTYPE_XML)) {
return NS_ERROR_INVALID_ARG;
}
// Make sure to hold the new vp in case it changed.
jsonVal = *vp;
nsJSONWriter writer;
ok = JS_Stringify(cx, jsonVal.ToJSValPtr(), NULL, &WriteCallback, &writer);
if (!ok) {
return NS_ERROR_XPC_BAD_CONVERT_JS;
}
NS_ENSURE_TRUE(writer.DidWrite(), NS_ERROR_UNEXPECTED);
writer.FlushBuffer();
aString.Assign(writer.mOutputString);
*aIsJSON = PR_TRUE;
return NS_OK;
}
class nsDOMWorkerScope : public nsIWorkerScope,
public nsIDOMEventTarget,
public nsIXPCScriptable,
@ -509,8 +618,7 @@ nsDOMWorkerScope::GetNavigator(nsIWorkerNavigator** _retval)
}
NS_IMETHODIMP
nsDOMWorkerScope::PostMessage(const nsAString& aMessage,
nsIWorkerMessagePort* aMessagePort)
nsDOMWorkerScope::PostMessage(/* JSObject aMessage */)
{
NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
@ -518,11 +626,13 @@ nsDOMWorkerScope::PostMessage(const nsAString& aMessage,
return NS_ERROR_ABORT;
}
if (aMessagePort) {
return NS_ERROR_NOT_IMPLEMENTED;
}
nsString message;
PRBool isJSON, isPrimitive;
return mWorker->PostMessageInternal(aMessage, PR_FALSE);
nsresult rv = GetStringForArgument(message, &isJSON, &isPrimitive);
NS_ENSURE_SUCCESS(rv, rv);
return mWorker->PostMessageInternal(message, isJSON, isPrimitive, PR_FALSE);
}
NS_IMETHODIMP
@ -1006,6 +1116,8 @@ nsDOMWorker::Resume()
nsresult
nsDOMWorker::PostMessageInternal(const nsAString& aMessage,
PRBool aIsJSON,
PRBool aIsPrimitive,
PRBool aToInner)
{
nsRefPtr<nsDOMWorkerMessageEvent> message = new nsDOMWorkerMessageEvent();
@ -1016,6 +1128,8 @@ nsDOMWorker::PostMessageInternal(const nsAString& aMessage,
EmptyString(), nsnull);
NS_ENSURE_SUCCESS(rv, rv);
message->SetJSONData(aIsJSON, aIsPrimitive);
nsRefPtr<nsDOMFireEventRunnable> runnable =
new nsDOMFireEventRunnable(this, message, aToInner);
NS_ENSURE_TRUE(runnable, NS_ERROR_OUT_OF_MEMORY);
@ -1324,18 +1438,19 @@ nsDOMWorker::GetParent()
* See nsIWorker
*/
NS_IMETHODIMP
nsDOMWorker::PostMessage(const nsAString& aMessage,
nsIWorkerMessagePort* aMessagePort)
nsDOMWorker::PostMessage(/* JSObject aMessage */)
{
if (mTerminated) {
return NS_OK;
}
if (aMessagePort) {
return NS_ERROR_NOT_IMPLEMENTED;
}
nsString message;
PRBool isJSON, isPrimitive;
return PostMessageInternal(aMessage, PR_TRUE);
nsresult rv = GetStringForArgument(message, &isJSON, &isPrimitive);
NS_ENSURE_SUCCESS(rv, rv);
return PostMessageInternal(message, isJSON, isPrimitive, PR_TRUE);
}
/**

View File

@ -148,6 +148,8 @@ private:
~nsDOMWorker();
nsresult PostMessageInternal(const nsAString& aMessage,
PRBool aIsJSON,
PRBool aIsPrimitive,
PRBool aToInner);
PRBool CompileGlobalObject(JSContext* aCx);

View File

@ -39,7 +39,10 @@
#include "nsDOMWorkerEvents.h"
#include "nsIXMLHttpRequest.h"
#include "nsIXPConnect.h"
#include "nsAXPCNativeCallContext.h"
#include "nsContentUtils.h"
#include "nsThreadUtils.h"
#include "nsDOMWorkerMessageHandler.h"
@ -240,8 +243,70 @@ NS_IMPL_THREADSAFE_DOM_CI_GETINTERFACES(nsDOMWorkerMessageEvent)
NS_IMETHODIMP
nsDOMWorkerMessageEvent::GetData(nsAString& aData)
{
aData.Assign(mData);
return NS_OK;
if (!mIsJSON) {
aData.Assign(mData);
return NS_OK;
}
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);
if (mCachedJSVal) {
*retval = mCachedJSVal;
return cc->SetReturnValueWasSet(PR_TRUE);
}
JSContext* cx;
rv = cc->GetJSContext(&cx);
NS_ENSURE_SUCCESS(rv, rv);
JSAutoRequest ar(cx);
JSBool ok = mCachedJSVal.Hold(cx);
NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
JSONParser* parser = JS_BeginJSONParse(cx, mCachedJSVal.ToJSValPtr());
NS_ENSURE_TRUE(parser, NS_ERROR_UNEXPECTED);
// This is slightly sneaky, but now that JS_BeginJSONParse succeeded we always
// need call JS_FinishJSONParse even if JS_ConsumeJSONText fails. We'll report
// an error if either failed, though.
ok = JS_ConsumeJSONText(cx, parser, (jschar*)mData.get(),
(uint32)mData.Length());
// Note the '&& ok' after the call here!
ok = JS_FinishJSONParse(cx, parser) && ok;
if (!ok) {
mCachedJSVal = JSVAL_NULL;
return NS_ERROR_UNEXPECTED;
}
NS_ASSERTION(mCachedJSVal.ToJSObject(), "Bad JSON result!");
if (mIsPrimitive) {
jsval primitive;
ok = JS_GetProperty(cx, mCachedJSVal.ToJSObject(), JSON_PRIMITIVE_PROPNAME,
&primitive);
if (!ok) {
mCachedJSVal = JSVAL_NULL;
return NS_ERROR_UNEXPECTED;
}
mCachedJSVal = primitive;
}
*retval = mCachedJSVal;
return cc->SetReturnValueWasSet(PR_TRUE);
}
NS_IMETHODIMP

View File

@ -46,6 +46,8 @@
#include "nsIDOMWorkers.h"
#include "nsIRunnable.h"
#include "jsapi.h"
#include "nsAutoJSValHolder.h"
#include "nsAutoPtr.h"
#include "nsCOMPtr.h"
#include "nsStringGlue.h"
@ -186,8 +188,20 @@ public:
NS_DECL_NSIWORKERMESSAGEEVENT
NS_DECL_NSICLASSINFO_GETINTERFACES
nsDOMWorkerMessageEvent()
: mIsJSON(PR_FALSE), mIsPrimitive(PR_FALSE) { }
void SetJSONData(PRBool aIsJSON, PRBool aIsPrimitive) {
mIsJSON = aIsJSON;
mIsPrimitive = aIsPrimitive;
}
protected:
nsString mData;
PRBool mIsJSON;
PRBool mIsPrimitive;
nsAutoJSValHolder mCachedJSVal;
nsString mOrigin;
nsCOMPtr<nsISupports> mSource;
};

View File

@ -129,4 +129,7 @@ NS_IMPL_THREADSAFE_DOM_CI_ALL_THE_REST(_class)
NS_IMETHOD_(nsrefcnt) AddRef(void) { return _to AddRef(); } \
NS_IMETHOD_(nsrefcnt) Release(void) { return _to Release(); }
#define JSON_PRIMITIVE_PROPNAME \
"primitive"
#endif /* __NSDOMWORKERMACROS_H__ */

View File

@ -56,6 +56,8 @@ _TEST_FILES = \
importScripts_worker_imported2.js \
importScripts_worker_imported3.js \
importScripts_worker_imported4.js \
test_json.html \
json_worker.js \
test_longThread.html \
longThread_worker.js \
test_navigator.html \

View File

@ -0,0 +1,203 @@
var messages = [
{
type: "object",
array: false,
exception: false,
shouldCompare: false,
shouldEqual: false,
value: { foo: "bar" }
},
{
type: "object",
array: true,
exception: false,
shouldCompare: false,
shouldEqual: false,
value: [9, 8, 7]
},
{
type: "object",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: null
},
{
type: "undefined",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: undefined,
compareValue: undefined
},
{
type: "string",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: "Hello"
},
{
type: "string",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: JSON.stringify({ foo: "bar" })
},
{
type: "number",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: 1
},
{
type: "number",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: 0
},
{
type: "number",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: -1
},
{
type: "number",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: 238573459843702923492399923049
},
{
type: "number",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: -238573459843702923492399923049
},
{
type: "number",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: 0.25
},
{
type: "number",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: -0.25
},
{
type: "boolean",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: true
},
{
type: "boolean",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: false
},
/*
// Uncomment these once bug 465371 is fixed!
{
type: "function",
array: false,
exception: true,
shouldCompare: false,
shouldEqual: false,
value: function (foo) { return "Bad!"; }
},
{
type: "xml",
array: false,
exception: true,
shouldCompare: true,
shouldEqual: true,
value: <funtimes></funtimes>
},
*/
{
type: "object",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: NaN,
compareValue: null
},
{
type: "object",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: Infinity,
compareValue: null
},
{
type: "object",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: -Infinity,
compareValue: null
},
{
type: "string",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: "testFinished"
}
];
for (var index = 0; index < messages.length; index++) {
var message = messages[index];
if (message.hasOwnProperty("compareValue")) {
continue;
}
message.compareValue = message.value;
}
var onmessage = function(event) {
for (var index = 0; index < messages.length; index++) {
var exception = false;
try {
postMessage(messages[index].value);
}
catch (e) {
exception = true;
}
if (messages[index].exception != exception) {
throw "Exception inconsistency!";
}
}
}

View File

@ -0,0 +1,67 @@
<!DOCTYPE HTML>
<html>
<!--
Tests of DOM Worker JSON messages
-->
<head>
<title>Test for DOM Worker Navigator</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script src="json_worker.js" language="javascript"></script>
<script class="testbody" language="javascript">
ok(messages.length, "No messages to test!");
var worker = new Worker("json_worker.js");
var index = 0;
worker.onmessage = function(event) {
var key = messages[index++];
// Loop for the ones we shouldn't receive.
while (key.exception) {
key = messages[index++];
}
is(typeof event.data, key.type,
"Bad type! " + messages.indexOf(key));
is(event.data instanceof Array, key.array,
"Array mismatch! " + messages.indexOf(key));
if (key.shouldCompare) {
ok(event.data == key.compareValue,
"Values don't compare! " + messages.indexOf(key));
}
if (key.shouldEqual) {
ok(event.data === key.compareValue,
"Values don't equal!" + messages.indexOf(key));
}
if (event.data == "testFinished") {
is(index, messages.length, "Didn't see the right number of messages!");
SimpleTest.finish();
}
};
worker.onerror = function(event) {
ok(false, "Worker had an error: " + event.data);
SimpleTest.finish();
}
worker.postMessage("start");
SimpleTest.waitForExplicitFinish();
</script>
</pre>
</body>
</html>

View File

@ -24,7 +24,7 @@ Tests of DOM Worker Threads XHR(Bug 450452 )
var worker = new Worker("xhrAbort_worker.js");
worker.onmessage = function(event) {
is (data, event.data, "Got different results!");
is (data.toString(), event.data.toString(), "Got different results!");
SimpleTest.finish();
};

View File

@ -127,6 +127,10 @@ public:
: NULL;
}
jsval* ToJSValPtr() {
return &mVal;
}
/**
* Pretend to be a jsval.
*/