Bug 550275 - 'Implement the HTML5 structured clone algorithm'. Make Web Workers use the new clones. r=jst
authorBen Turner <bent.mozilla@gmail.com>
Wed, 17 Mar 2010 12:56:49 -0700
changeset 39542 2e9bb82a9eb3fafd22d6ff9b63db6f702483a6f3
parent 39541 5ae1a1d05ebb6019028f8079c4057c840e3dd6e9
child 39543 5e0706a64bbaf174162326f084f6afcaadbc3014
push id12256
push userbturner@mozilla.com
push dateWed, 17 Mar 2010 19:59:41 +0000
treeherdermozilla-central@2e9bb82a9eb3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjst
bugs550275
milestone1.9.3a4pre
Bug 550275 - 'Implement the HTML5 structured clone algorithm'. Make Web Workers use the new clones. r=jst
dom/src/threads/nsDOMThreadService.cpp
dom/src/threads/nsDOMThreadService.h
dom/src/threads/nsDOMWorker.cpp
dom/src/threads/nsDOMWorkerEvents.cpp
dom/src/threads/nsDOMWorkerEvents.h
dom/src/threads/test/json_worker.js
dom/src/threads/test/test_json.html
--- a/dom/src/threads/nsDOMThreadService.cpp
+++ b/dom/src/threads/nsDOMThreadService.cpp
@@ -123,26 +123,16 @@ PRUintn gJSContextIndex = BAD_TLS_INDEX;
 
 static const char* sPrefsToWatch[] = {
   "dom.max_script_run_time"
 };
 
 // The length of time the close handler is allowed to run in milliseconds.
 static PRUint32 gWorkerCloseHandlerTimeoutMS = 10000;
 
-static int sStringFinalizerIndex = -1;
-
-static void
-StringFinalizer(JSContext* aCx,
-                JSString* aStr)
-{
-  NS_ASSERTION(aStr, "Null string!");
-  nsStringBuffer::FromData(JS_GetStringChars(aStr))->Release();
-}
-
 /**
  * Simple class to automatically destroy a JSContext to make error handling
  * easier.
  */
 class JSAutoContextDestroyer
 {
 public:
   JSAutoContextDestroyer(JSContext* aCx)
@@ -715,31 +705,27 @@ NS_IMPL_THREADSAFE_ISUPPORTS3(nsDOMThrea
                                                   nsIObserver,
                                                   nsIThreadPoolListener)
 
 nsresult
 nsDOMThreadService::Init()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(!gDOMThreadService, "Only one instance should ever be created!");
-  NS_ASSERTION(sStringFinalizerIndex == -1, "String finalizer already set!");
 
   nsresult rv;
   nsCOMPtr<nsIObserverService> obs =
     do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_FALSE);
   NS_ENSURE_SUCCESS(rv, rv);
 
   obs.forget(&gObserverService);
 
-  sStringFinalizerIndex = JS_AddExternalStringFinalizer(StringFinalizer);
-  NS_ENSURE_TRUE(sStringFinalizerIndex != -1, NS_ERROR_FAILURE);
-
   RegisterPrefCallbacks();
 
   mThreadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mThreadPool->SetListener(this);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -1244,48 +1230,16 @@ nsDOMThreadService::ThreadJSContextStack
 
 // static
 nsIXPCSecurityManager*
 nsDOMThreadService::WorkerSecurityManager()
 {
   return gWorkerSecurityManager;
 }
 
-// static
-jsval
-nsDOMThreadService::ShareStringAsJSVal(JSContext* aCx,
-                                       const nsAString& aString)
-{
-  NS_ASSERTION(sStringFinalizerIndex != -1, "Bad index!");
-  NS_ASSERTION(aCx, "Null context!");
-
-  PRUint32 length = aString.Length();
-  if (!length) {
-    JSAtom* atom = aCx->runtime->atomState.emptyAtom;
-    return ATOM_KEY(atom);
-  }
-
-  nsStringBuffer* buf = nsStringBuffer::FromString(aString);
-  if (!buf) {
-    NS_WARNING("Can't share this string buffer!");
-    return JSVAL_VOID;
-  }
-
-  JSString* str =
-    JS_NewExternalString(aCx, reinterpret_cast<jschar*>(buf->Data()), length,
-                         sStringFinalizerIndex);
-  if (str) {
-    buf->AddRef();
-    return STRING_TO_JSVAL(str);
-  }
-
-  NS_WARNING("JS_NewExternalString failed!");
-  return JSVAL_VOID;
-}
-
 /**
  * See nsIEventTarget
  */
 NS_IMETHODIMP
 nsDOMThreadService::Dispatch(nsIRunnable* aEvent,
                              PRUint32 aFlags)
 {
   NS_ENSURE_ARG_POINTER(aEvent);
--- a/dom/src/threads/nsDOMThreadService.h
+++ b/dom/src/threads/nsDOMThreadService.h
@@ -103,19 +103,16 @@ public:
 
   static JSContext* GetCurrentContext();
 
   // Easy access to the services we care about.
   static nsIJSRuntimeService* JSRuntimeService();
   static nsIThreadJSContextStack* ThreadJSContextStack();
   static nsIXPCSecurityManager* WorkerSecurityManager();
 
-  static jsval ShareStringAsJSVal(JSContext* aCx,
-                                  const nsAString& aString);
-
   void CancelWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject);
   void SuspendWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject);
   void ResumeWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject);
 
   nsresult ChangeThreadPoolMaxThreads(PRInt16 aDelta);
 
 private:
   nsDOMThreadService();
--- a/dom/src/threads/nsDOMWorker.cpp
+++ b/dom/src/threads/nsDOMWorker.cpp
@@ -396,115 +396,16 @@ 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(JSContext* aCx,
-                     jsval aVal,
-                     PRBool* aIsJSON,
-                     PRBool* aIsPrimitive,
-                     nsAutoJSValHolder& _retval)
-{
-  NS_ASSERTION(aIsJSON && aIsPrimitive, "Null pointer!");
-
-  if (JSVAL_IS_STRING(aVal)) {
-    if (!JS_MakeStringImmutable(aCx, JSVAL_TO_STRING(aVal))) {
-      return NS_ERROR_FAILURE;
-    }
-
-    *aIsJSON = *aIsPrimitive = PR_FALSE;
-    _retval = aVal;
-    return NS_OK;
-  }
-
-  nsAutoJSValHolder jsonVal;
-
-  JSBool ok = jsonVal.Hold(aCx);
-  NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
-
-  if (JSVAL_IS_PRIMITIVE(aVal)) {
-    // 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(aCx, NULL, NULL, NULL);
-    NS_ENSURE_TRUE(obj, NS_ERROR_OUT_OF_MEMORY);
-
-    jsonVal = obj;
-
-    ok = JS_DefineProperty(aCx, obj, JSON_PRIMITIVE_PROPNAME, aVal, NULL,
-                           NULL, JSPROP_ENUMERATE);
-    NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
-
-    *aIsPrimitive = PR_TRUE;
-  }
-  else {
-    jsonVal = aVal;
-
-    *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(aCx, vp);
-  if (!(ok && !JSVAL_IS_PRIMITIVE(*vp) &&
-        (type = JS_TypeOfValue(aCx, *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(aCx, jsonVal.ToJSValPtr(), NULL, JSVAL_NULL, WriteCallback,
-                    &writer);
-  if (!ok) {
-    return NS_ERROR_XPC_BAD_CONVERT_JS;
-  }
-
-  NS_ENSURE_TRUE(writer.DidWrite(), NS_ERROR_UNEXPECTED);
-
-  writer.FlushBuffer();
-
-  _retval = nsDOMThreadService::ShareStringAsJSVal(aCx, writer.mOutputString);
-  if (!JSVAL_IS_STRING(_retval)) {
-    // Yuck, we can't share.
-    const jschar* buf =
-      reinterpret_cast<const jschar*>(writer.mOutputString.get());
-    JSString* str = JS_NewUCStringCopyN(aCx, buf, writer.mOutputString.Length());
-    if (!str) {
-      JS_ReportOutOfMemory(aCx);
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-    _retval = STRING_TO_JSVAL(str);
-  }
-
-  *aIsJSON = PR_TRUE;
-  return NS_OK;
-}
-
 nsDOMWorkerScope::nsDOMWorkerScope(nsDOMWorker* aWorker)
 : mWorker(aWorker),
   mWrappedNative(nsnull),
   mHasOnerror(PR_FALSE)
 {
   NS_ASSERTION(aWorker, "Null pointer!");
 }
 
@@ -1501,31 +1402,30 @@ nsDOMWorker::PostMessageInternal(PRBool 
 
   JSAutoRequest ar(cx);
 
   nsAutoJSValHolder val;
   if (!val.Hold(cx)) {
     return NS_ERROR_FAILURE;
   }
 
-  PRBool isJSON, isPrimitive;
-  rv = GetStringForArgument(cx, argv[0], &isJSON, &isPrimitive, val);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  NS_ASSERTION(JSVAL_IS_STRING(val), "Bad jsval!");
+  rv = nsContentUtils::CreateStructuredClone(cx, argv[0], val.ToJSValPtr());
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
 
   nsRefPtr<nsDOMWorkerMessageEvent> message = new nsDOMWorkerMessageEvent();
   NS_ENSURE_TRUE(message, NS_ERROR_OUT_OF_MEMORY);
 
   rv = message->InitMessageEvent(NS_LITERAL_STRING("message"), PR_FALSE,
                                  PR_FALSE, EmptyString(), EmptyString(),
                                  nsnull);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = message->SetJSONData(cx, val, isJSON, isPrimitive);
+  rv = message->SetJSVal(cx, val);
   NS_ENSURE_SUCCESS(rv, rv);
 
   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.
--- a/dom/src/threads/nsDOMWorkerEvents.cpp
+++ b/dom/src/threads/nsDOMWorkerEvents.cpp
@@ -259,26 +259,19 @@ NS_IMPL_ISUPPORTS_INHERITED1(nsDOMWorker
                                                       nsIWorkerMessageEvent)
 
 NS_IMPL_CI_INTERFACE_GETTER2(nsDOMWorkerMessageEvent, nsIDOMEvent,
                                                       nsIWorkerMessageEvent)
 
 NS_IMPL_THREADSAFE_DOM_CI_GETINTERFACES(nsDOMWorkerMessageEvent)
 
 nsresult
-nsDOMWorkerMessageEvent::SetJSONData(JSContext* aCx,
-                                     jsval aData,
-                                     PRBool aIsJSON,
-                                     PRBool aIsPrimitive)
+nsDOMWorkerMessageEvent::SetJSVal(JSContext* aCx,
+                                  jsval aData)
 {
-  NS_ASSERTION(JSVAL_IS_STRING(aData), "Bad jsval!");
-
-  mIsJSON = aIsJSON ? PR_TRUE : PR_FALSE;
-  mIsPrimitive = aIsPrimitive ? PR_TRUE : PR_FALSE;
-
   if (!mDataVal.Hold(aCx)) {
     NS_WARNING("Failed to hold jsval!");
     return NS_ERROR_FAILURE;
   }
 
   mDataVal = aData;
   return NS_OK;
 }
@@ -289,90 +282,37 @@ nsDOMWorkerMessageEvent::GetData(nsAStri
   nsIXPConnect* xpc = nsContentUtils::XPConnect();
   NS_ENSURE_TRUE(xpc, NS_ERROR_UNEXPECTED);
 
   nsAXPCNativeCallContext* cc;
   nsresult rv = xpc->GetCurrentNativeCallContext(&cc);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(cc, NS_ERROR_UNEXPECTED);
 
+  if (!mDataValWasReparented) {
+    if (JSVAL_IS_OBJECT(mDataVal) && !JSVAL_IS_NULL(mDataVal)) {
+      JSContext* cx;
+      rv = cc->GetJSContext(&cx);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      rv =
+        nsContentUtils::ReparentClonedObjectToScope(cx,
+                                                    JSVAL_TO_OBJECT(mDataVal),
+                                                    JS_GetGlobalObject(cx));
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+    mDataValWasReparented = PR_TRUE;
+  }
+
   jsval* retval;
   rv = cc->GetRetValPtr(&retval);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (!mIsJSON) {
-    cc->SetReturnValueWasSet(PR_TRUE);
-    *retval = mDataVal;
-    return NS_OK;
-  }
-
-  if (mHaveCachedJSVal) {
-    cc->SetReturnValueWasSet(PR_TRUE);
-    *retval = mCachedJSVal;
-    return NS_OK;
-  }
-
-  if (mHaveAttemptedConversion) {
-    // Don't try to convert again if the first time around we saw an error.
-    return NS_ERROR_FAILURE;
-  }
-  mHaveAttemptedConversion = 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);
-
-  NS_ASSERTION(JSVAL_IS_STRING(mDataVal), "Bad jsval!");
-  JSString* str = JSVAL_TO_STRING(mDataVal);
-
-  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, JS_GetStringChars(str),
-                          JS_GetStringLength(str));
-
-  // Note the '&& ok' after the call here!
-  ok = JS_FinishJSONParse(cx, parser, JSVAL_NULL) && 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;
-  }
-
-  // We no longer need to hold this copy of the data around.
-  mDataVal.Release();
-
-  // Now that everything has succeeded we'll set this flag so that we return the
-  // cached jsval in the future.
-  mHaveCachedJSVal = PR_TRUE;
-
-  *retval = mCachedJSVal;
   cc->SetReturnValueWasSet(PR_TRUE);
+  *retval = mDataVal;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWorkerMessageEvent::GetOrigin(nsAString& aOrigin)
 {
   aOrigin.Assign(mOrigin);
   return NS_OK;
--- a/dom/src/threads/nsDOMWorkerEvents.h
+++ b/dom/src/threads/nsDOMWorkerEvents.h
@@ -205,36 +205,27 @@ 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), mHaveCachedJSVal(PR_FALSE),
-    mHaveAttemptedConversion(PR_FALSE) { }
+  nsDOMWorkerMessageEvent() : mDataValWasReparented(PR_FALSE) { }
 
-  nsresult SetJSONData(JSContext* aCx,
-                       jsval aData,
-                       PRBool aIsJSON,
-                       PRBool aIsPrimitive);
+  nsresult SetJSVal(JSContext* aCx,
+                    jsval aData);
 
 protected:
   nsString mOrigin;
   nsCOMPtr<nsISupports> mSource;
 
   nsAutoJSValHolder mDataVal;
-  nsAutoJSValHolder mCachedJSVal;
-
-  PRPackedBool mIsJSON;
-  PRPackedBool mIsPrimitive;
-  PRPackedBool mHaveCachedJSVal;
-  PRPackedBool mHaveAttemptedConversion;
+  PRBool mDataValWasReparented;
 };
 
 class nsDOMWorkerProgressEvent : public nsDOMWorkerEvent,
                                  public nsIDOMProgressEvent
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_FORWARD_NSIDOMEVENT(nsDOMWorkerEvent::)
--- a/dom/src/threads/test/json_worker.js
+++ b/dom/src/threads/test/json_worker.js
@@ -1,203 +1,242 @@
+var cyclicalObject = {};
+cyclicalObject.foo = cyclicalObject;
+
+var cyclicalArray = [];
+cyclicalArray.push(cyclicalArray);
+
+function makeCrazyNested(obj, count) {
+  var innermostobj;
+  for (var i = 0; i < count; i++) {
+    obj.foo = { bar: 5 }
+    innermostobj = obj.foo;
+    obj = innermostobj;
+  }
+  return innermostobj;
+}
+
+var crazyNestedObject = {};
+makeCrazyNested(crazyNestedObject, 100);
+
+var crazyCyclicalObject = {};
+var innermost = makeCrazyNested(crazyCyclicalObject, 1000);
+innermost.baz = crazyCyclicalObject;
+
+var objectWithSaneGetter = { };
+objectWithSaneGetter.__defineGetter__("foo", function() { return 5; });
+
+// We don't walk prototype chains for cloning so this won't actually do much...
+function objectWithSaneGetter2() { }
+objectWithSaneGetter2.prototype = {
+  get foo() {
+    return 5;
+  }
+};
+
+var objectWithThrowingGetter = { };
+objectWithThrowingGetter.__defineGetter__("foo", function() { throw "bad"; });
+
 var messages = [
   {
     type: "object",
-    array: false,
-    exception: false,
-    shouldCompare: false,
-    shouldEqual: false,
-    value: { foo: "bar" }
+    value: { },
+    jsonValue: '{}'
+  },
+  {
+    type: "object",
+    value: {foo: "bar"},
+    jsonValue: '{"foo":"bar"}'
+  },
+  {
+    type: "object",
+    value: {foo: "bar", foo2: {bee: "bop"}},
+    jsonValue: '{"foo":"bar","foo2":{"bee":"bop"}}'
+  },
+  {
+    type: "object",
+    value: {foo: "bar", foo2: {bee: "bop"}, foo3: "baz"},
+    jsonValue: '{"foo":"bar","foo2":{"bee":"bop"},"foo3":"baz"}'
+  },
+  {
+    type: "object",
+    value: {foo: "bar", foo2: [1,2,3]},
+    jsonValue: '{"foo":"bar","foo2":[1,2,3]}'
+  },
+  {
+    type: "object",
+    value: cyclicalObject,
+    exception: true
+  },
+  {
+    type: "object",
+    value: [null, 2, false, cyclicalObject],
+    exception: true
+  },
+  {
+    type: "object",
+    value: cyclicalArray,
+    exception: true
+  },
+  {
+    type: "object",
+    value: {foo: 1, bar: cyclicalArray},
+    exception: true
+  },
+  {
+    type: "object",
+    value: crazyNestedObject,
+    jsonValue: JSON.stringify(crazyNestedObject)
+  },
+  {
+    type: "object",
+    value: crazyCyclicalObject,
+    exception: true
+  },
+  {
+    type: "object",
+    value: objectWithSaneGetter,
+    jsonValue: '{"foo":5}'
+  },
+  {
+    type: "object",
+    value: new objectWithSaneGetter2(),
+    jsonValue: '{}'
+  },
+  {
+    type: "object",
+    value: objectWithThrowingGetter,
+    exception: true
   },
   {
     type: "object",
     array: true,
-    exception: false,
-    shouldCompare: false,
-    shouldEqual: false,
-    value: [9, 8, 7]
+    value: [9, 8, 7],
+    jsonValue: '[9,8,7]'
   },
   {
     type: "object",
-    array: false,
-    exception: false,
-    shouldCompare: true,
+    array: true,
+    value: [9, false, 10.5, {foo: "bar"}],
+    jsonValue: '[9,false,10.5,{"foo":"bar"}]'
+  },
+  {
+    type: "object",
     shouldEqual: true,
     value: null
   },
   {
     type: "undefined",
-    array: false,
-    exception: false,
-    shouldCompare: true,
     shouldEqual: true,
-    value: undefined,
-    compareValue: undefined
+    value: 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" })
+    value: JSON.stringify({ foo: "bar" }),
+    compareValue: '{"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,
+    value: function (foo) { return "Bad!"; },
     compareValue: null
   },
   {
-    type: "object",
-    array: false,
-    exception: false,
-    shouldCompare: true,
-    shouldEqual: true,
-    value: Infinity,
-    compareValue: null
+    type: "number",
+    isNaN: true,
+    value: NaN
   },
   {
-    type: "object",
-    array: false,
-    exception: false,
-    shouldCompare: true,
-    shouldEqual: true,
-    value: -Infinity,
-    compareValue: null
+    type: "number",
+    isInfinity: true,
+    value: Infinity
+  },
+  {
+    type: "number",
+    isNegativeInfinity: true,
+    value: -Infinity
   },
   {
     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;
+  if (message.hasOwnProperty("shouldEqual") ||
+      message.hasOwnProperty("shouldCompare")) {
+    message.compareValue = message.value;
+  }
 }
 
-var onmessage = function(event) {
+function onmessage(event) {
   for (var index = 0; index < messages.length; index++) {
-    var exception = false;
+    var exception = undefined;
 
     try {
       postMessage(messages[index].value);
     }
     catch (e) {
-      exception = true;
+      exception = e;
     }
 
-    if (messages[index].exception != exception) {
-      throw "Exception inconsistency!";
+    if ((exception !== undefined && !messages[index].exception) ||
+        (exception === undefined && messages[index].exception)) {
+      throw "Exception inconsistency [" + index + "]: " + exception;
     }
   }
 }
--- a/dom/src/threads/test/test_json.html
+++ b/dom/src/threads/test/test_json.html
@@ -26,39 +26,58 @@ Tests of DOM Worker JSON messages
   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));
+    is(typeof event.data, key.type, "Bad type! " + messages.indexOf(key));
+
+    if (key.array) {
+      is(event.data instanceof Array, key.array,
+         "Array mismatch! " + messages.indexOf(key));
+    }
 
-    if (key.shouldCompare) {
+    if (key.isNaN) {
+      ok(isNaN(event.data), "Should be NaN!" + messages.indexOf(key));
+    }
+
+    if (key.isInfinity) {
+      is(event.data, Infinity, "Should be Infinity!" + messages.indexOf(key));
+    }
+
+    if (key.isNegativeInfinity) {
+      is(event.data, -Infinity, "Should be -Infinity!" + messages.indexOf(key));
+    }
+
+    if (key.shouldCompare || key.shouldEqual) {
       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));
+         "Values don't equal! " + messages.indexOf(key));
+    }
+
+    if (key.jsonValue) {
+      is(JSON.stringify(event.data), key.jsonValue,
+         "Object stringification inconsistent!" + 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);
+    ok(false, "Worker had an error: " + event.message);
     SimpleTest.finish();
   }
 
   worker.postMessage("start");
 
   SimpleTest.waitForExplicitFinish();
 
 </script>