Bug 453865 - 'Workers: Allow JSON-able objects to be passed as messages to worker threads.' r+sr+a=jst.
authorBen Turner <bent.mozilla@gmail.com>
Sun, 07 Dec 2008 16:15:49 -0800
changeset 22477 b4bbb3c351a60b7561e2cb5f6c09a478704f11db
parent 22476 411f1455e3236d8315b8fe5f37e4d568dab86c78
child 22478 3125e01b21649e11c0c0d1876a3b67599fddfbaf
push id4021
push userbturner@mozilla.com
push dateMon, 08 Dec 2008 00:17:24 +0000
treeherdermozilla-central@242a58a529d7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs453865
milestone1.9.2a1pre
Bug 453865 - 'Workers: Allow JSON-able objects to be passed as messages to worker threads.' r+sr+a=jst.
dom/public/idl/threads/nsIDOMWorkers.idl
dom/src/threads/Makefile.in
dom/src/threads/nsDOMWorker.cpp
dom/src/threads/nsDOMWorker.h
dom/src/threads/nsDOMWorkerEvents.cpp
dom/src/threads/nsDOMWorkerEvents.h
dom/src/threads/nsDOMWorkerMacros.h
dom/src/threads/test/Makefile.in
dom/src/threads/test/json_worker.js
dom/src/threads/test/test_json.html
dom/src/threads/test/test_xhrAbort.html
js/src/xpconnect/public/nsAutoJSValHolder.h
--- a/dom/public/idl/threads/nsIDOMWorkers.idl
+++ b/dom/public/idl/threads/nsIDOMWorkers.idl
@@ -40,20 +40,20 @@
  * From http://www.whatwg.org/specs/web-workers/current-work
  */
 
 #include "nsIDOMEvent.idl"
 #include "nsIDOMEventTarget.idl"
 
 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)]
 interface nsIWorkerMessageEvent : nsIDOMEvent
 {
   readonly attribute DOMString data;
   readonly attribute DOMString origin;
 
@@ -78,33 +78,31 @@ interface nsIWorkerNavigator : nsISuppor
 
 [scriptable, uuid(a41ac154-ba18-4926-8954-cd6973ea490e)]
 interface nsIWorkerGlobalScope : nsISupports
 {
   readonly attribute nsIWorkerGlobalScope self;
   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;
 };
 
 [scriptable, uuid(b90b7561-b5e2-4545-84b0-280dbaaa94ea)]
 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;
 
   void terminate();
 };
--- a/dom/src/threads/Makefile.in
+++ b/dom/src/threads/Makefile.in
@@ -56,16 +56,17 @@ REQUIRES = \
   js \
   layout \
   locale \
   necko \
   plugin \
   pref \
   string \
   thebes \
+  uconv \
   widget \
   xpcom \
   xpconnect \
   $(NULL)
 
 CPPSRCS = \
   nsDOMThreadService.cpp \
   nsDOMWorker.cpp \
@@ -77,16 +78,17 @@ CPPSRCS = \
   nsDOMWorkerSecurityManager.cpp \
   nsDOMWorkerTimeout.cpp \
   nsDOMWorkerXHR.cpp \
   nsDOMWorkerXHRProxy.cpp \
   $(NULL)
 
 LOCAL_INCLUDES = \
   -I$(topsrcdir)/dom/src/base \
+  -I$(topsrcdir)/dom/src/json \
   -I$(topsrcdir)/content/base/src \
   -I$(topsrcdir)/content/events/src \
   $(NULL)
 
 ifdef ENABLE_TESTS
 DIRS += test
 endif
 
--- a/dom/src/threads/nsDOMWorker.cpp
+++ b/dom/src/threads/nsDOMWorker.cpp
@@ -42,19 +42,21 @@
 #include "nsIEventTarget.h"
 #include "nsIJSRuntimeService.h"
 #include "nsIXPConnect.h"
 
 #ifdef MOZ_SHARK
 #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"
 
 #include "nsDOMThreadService.h"
 #include "nsDOMWorkerEvents.h"
 #include "nsDOMWorkerNavigator.h"
 #include "nsDOMWorkerPool.h"
@@ -408,16 +410,123 @@ JSFunctionSpec gDOMWorkerFunctions[] = {
   { "startShark",            js_StartShark,                           0, 0, 0 },
   { "stopShark",             js_StopShark,                            0, 0, 0 },
   { "connectShark",          js_ConnectShark,                         0, 0, 0 },
   { "disconnectShark",       js_DisconnectShark,                      0, 0, 0 },
 #endif
   { nsnull,                  nsnull,                                  0, 0, 0 }
 };
 
+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,
                          public nsIClassInfo
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIWORKERGLOBALSCOPE
@@ -504,30 +613,31 @@ nsDOMWorkerScope::GetNavigator(nsIWorker
     NS_ENSURE_TRUE(mNavigator, NS_ERROR_OUT_OF_MEMORY);
   }
 
   NS_ADDREF(*_retval = mNavigator);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDOMWorkerScope::PostMessage(const nsAString& aMessage,
-                              nsIWorkerMessagePort* aMessagePort)
+nsDOMWorkerScope::PostMessage(/* JSObject aMessage */)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
   if (mWorker->IsCanceled()) {
     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
 nsDOMWorkerScope::GetOnmessage(nsIDOMEventListener** aOnmessage)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
   NS_ENSURE_ARG_POINTER(aOnmessage);
 
@@ -1001,26 +1111,30 @@ nsDOMWorker::Resume()
   NS_ASSERTION(mSuspended, "Not suspended!");
   mSuspended = PR_FALSE;
 
   ResumeFeatures();
 }
 
 nsresult
 nsDOMWorker::PostMessageInternal(const nsAString& aMessage,
+                                 PRBool aIsJSON,
+                                 PRBool aIsPrimitive,
                                  PRBool aToInner)
 {
   nsRefPtr<nsDOMWorkerMessageEvent> message = new nsDOMWorkerMessageEvent();
   NS_ENSURE_TRUE(message, NS_ERROR_OUT_OF_MEMORY);
 
   nsresult rv = message->InitMessageEvent(NS_LITERAL_STRING("message"),
                                           PR_FALSE, PR_FALSE, 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);
 
   // 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;
 
@@ -1319,28 +1433,29 @@ nsDOMWorker::GetParent()
   nsRefPtr<nsDOMWorker> parent(mParent);
   return parent.forget();
 }
 
 /**
  * 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);
 }
 
 /**
  * See nsIWorker
  */
 NS_IMETHODIMP
 nsDOMWorker::GetOnerror(nsIDOMEventListener** aOnerror)
 {
--- a/dom/src/threads/nsDOMWorker.h
+++ b/dom/src/threads/nsDOMWorker.h
@@ -143,16 +143,18 @@ public:
   nsDOMWorkerScope* GetInnerScope() {
     return mInnerScope;
   }
 
 private:
   ~nsDOMWorker();
 
   nsresult PostMessageInternal(const nsAString& aMessage,
+                               PRBool aIsJSON,
+                               PRBool aIsPrimitive,
                                PRBool aToInner);
 
   PRBool CompileGlobalObject(JSContext* aCx);
 
   PRUint32 NextTimeoutId() {
     return mNextTimeoutId++;
   }
 
--- a/dom/src/threads/nsDOMWorkerEvents.cpp
+++ b/dom/src/threads/nsDOMWorkerEvents.cpp
@@ -34,17 +34,20 @@
  * 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 "nsAXPCNativeCallContext.h"
+#include "nsContentUtils.h"
 #include "nsThreadUtils.h"
 
 #include "nsDOMWorkerMessageHandler.h"
 #include "nsDOMWorkerXHR.h"
 #include "nsDOMWorkerXHRProxy.h"
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIDOMWorkerPrivateEvent,
                               NS_IDOMWORKERPRIVATEEVENT_IID)
@@ -235,18 +238,80 @@ NS_IMPL_ISUPPORTS_INHERITED1(nsDOMWorker
 NS_IMPL_CI_INTERFACE_GETTER2(nsDOMWorkerMessageEvent, nsIDOMEvent,
                                                       nsIWorkerMessageEvent)
 
 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
 nsDOMWorkerMessageEvent::GetOrigin(nsAString& aOrigin)
 {
   aOrigin.Assign(mOrigin);
   return NS_OK;
 }
--- a/dom/src/threads/nsDOMWorkerEvents.h
+++ b/dom/src/threads/nsDOMWorkerEvents.h
@@ -41,16 +41,18 @@
 
 #include "nsIClassInfo.h"
 #include "nsIDOMEvent.h"
 #include "nsIDOMEventTarget.h"
 #include "nsIDOMProgressEvent.h"
 #include "nsIDOMWorkers.h"
 #include "nsIRunnable.h"
 
+#include "jsapi.h"
+#include "nsAutoJSValHolder.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsStringGlue.h"
 
 #include "nsDOMWorkerMacros.h"
 
 class nsDOMWorkerXHRProxy;
 class nsIXMLHttpRequest;
@@ -181,18 +183,30 @@ class nsDOMWorkerMessageEvent : public n
                                 public nsIWorkerMessageEvent
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_FORWARD_NSIDOMEVENT(nsDOMWorkerEvent::)
   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;
 };
 
 class nsDOMWorkerProgressEvent : public nsDOMWorkerEvent,
                                  public nsIDOMProgressEvent
 {
 public:
--- a/dom/src/threads/nsDOMWorkerMacros.h
+++ b/dom/src/threads/nsDOMWorkerMacros.h
@@ -124,9 +124,12 @@ NS_IMPL_THREADSAFE_DOM_CI_ALL_THE_REST(_
 // 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__ */
--- a/dom/src/threads/test/Makefile.in
+++ b/dom/src/threads/test/Makefile.in
@@ -51,16 +51,18 @@ include $(topsrcdir)/config/rules.mk
   errorPropagation_worker1.js \
   errorPropagation_worker2.js \
   test_importScripts.html \
   importScripts_worker.js \
   importScripts_worker_imported1.js \
   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 \
   navigator_worker.js \
   test_recursion.html \
   recursion_worker.js \
   test_regExpStatics.html \
   regExpStatics_worker.js \
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/json_worker.js
@@ -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!";
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/test_json.html
@@ -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>
--- a/dom/src/threads/test/test_xhrAbort.html
+++ b/dom/src/threads/test/test_xhrAbort.html
@@ -19,17 +19,17 @@ Tests of DOM Worker Threads XHR(Bug 4504
 <script language="javascript" src="xhrAbort_worker.js"></script>
 <script class="testbody" language="javascript">
 
   function postMessage(data) {
 
     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();
     };
 
     worker.onerror = function(event) {
       ok(false, "Worker had an error: " + event.data);
       SimpleTest.finish();
     }
 
--- a/js/src/xpconnect/public/nsAutoJSValHolder.h
+++ b/js/src/xpconnect/public/nsAutoJSValHolder.h
@@ -122,16 +122,20 @@ public:
    * Explicit JSObject* conversion.
    */
   JSObject* ToJSObject() const {
     return JSVAL_IS_OBJECT(mVal)
          ? JSVAL_TO_OBJECT(mVal)
          : NULL;
   }
 
+  jsval* ToJSValPtr() {
+    return &mVal;
+  }
+
   /**
    * Pretend to be a jsval.
    */
   operator jsval() const { return mVal; }
 
   nsAutoJSValHolder &operator=(JSObject* aOther) {
 #ifdef DEBUG
     if (aOther) {