Bug 387522. Native JSON support. r=crowder/jst, sr=brendan
authorsayrer@gmail.com
Thu, 27 Dec 2007 13:34:03 -0800
changeset 9695 5dc8027b1b052a5e1218eff8a20a44baaad9e752
parent 9694 7b1a4174e271e4e763ff47bdc5d3905ad5968437
child 9696 918d3d8590d63ad7bee1f0b9108ef30d3d09bd4b
push id1
push userbsmedberg@mozilla.com
push dateThu, 20 Mar 2008 16:49:24 +0000
treeherdermozilla-central@61007906a1f8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscrowder, jst, brendan
bugs387522
milestone1.9b3pre
Bug 387522. Native JSON support. r=crowder/jst, sr=brendan
dom/public/idl/Makefile.in
dom/public/idl/json/Makefile.in
dom/public/idl/json/nsIJSON.idl
dom/src/Makefile.in
dom/src/json/Makefile.in
dom/src/json/nsJSON.cpp
dom/src/json/nsJSON.h
dom/src/json/test/Makefile.in
dom/src/json/test/json2.js
dom/src/json/test/pass1.json
dom/src/json/test/pass3.json
dom/src/json/test/unit/head_json.js
dom/src/json/test/unit/test_decode.js
dom/src/json/test/unit/test_encode.js
js/src/Makefile.in
js/src/jsiter.c
js/src/jsiter.h
layout/build/Makefile.in
layout/build/nsLayoutCID.h
layout/build/nsLayoutModule.cpp
layout/style/nsCSSLoader.cpp
tools/test-harness/xpcshell-simple/test_all.sh
--- a/dom/public/idl/Makefile.in
+++ b/dom/public/idl/Makefile.in
@@ -53,16 +53,17 @@ DIRS =						\
 	css					\
 	traversal				\
 	range					\
 	xbl					\
 	xpath					\
 	ls					\
 	xul                                     \
 	storage                                 \
+	json                                    \
 	offline
 
 ifdef MOZ_SVG
 DIRS += svg
 endif
 
 include $(topsrcdir)/config/rules.mk
 
new file mode 100644
--- /dev/null
+++ b/dom/public/idl/json/Makefile.in
@@ -0,0 +1,57 @@
+#
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2007
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Robert Sayre <sayrer@gmail.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# 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 *****
+
+DEPTH          = ../../../..
+topsrcdir      = @top_srcdir@
+srcdir         = @srcdir@
+VPATH          = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE         = dom
+XPIDL_MODULE   = dom_json
+GRE_MODULE     = 1
+
+XPIDLSRCS =                      \
+       nsIJSON.idl               \
+       $(NULL)
+
+SDK_XPIDLSRCS =                  \
+       nsIJSON.idl               \
+       $(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/public/idl/json/nsIJSON.idl
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Robert Sayre <sayrer@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * 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 "domstubs.idl"
+
+interface nsIInputStream;
+interface nsIOutputStream;
+interface nsIScriptGlobalObject;
+
+/**
+ * Encode and decode JSON text.
+ */
+[scriptable, uuid(45464c36-efde-4cb5-8e00-07480533ff35)]
+interface nsIJSON : nsISupports
+{
+  AString encode(/* in JSObject value,
+                 /* [optional] in JSObject whitelist */);
+
+  void encodeToStream(in nsIOutputStream stream,
+                      in string charset,
+                      in boolean writeBOM
+                      /* in JSObject value,
+                      /* [optional] in JSObject optFilter */);
+
+  void /* JSObject */ decode(in AString str
+                             /* , [optional] in JSObject whitelist */);
+
+  void /* JSObject */ decodeFromStream(in nsIInputStream stream,
+                                       in long contentLength
+                                       /*[optional] in JSObject optFilter */);
+};
--- a/dom/src/Makefile.in
+++ b/dom/src/Makefile.in
@@ -37,17 +37,17 @@
 
 DEPTH		= ../..
 topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
-DIRS		= base jsurl events storage offline
+DIRS		= base jsurl events storage offline json
 
 include $(topsrcdir)/config/rules.mk
 
 _FILES = \
 	$(srcdir)/res/hiddenWindow.html \
 	$(NULL)
 
 libs::
new file mode 100644
--- /dev/null
+++ b/dom/src/json/Makefile.in
@@ -0,0 +1,80 @@
+#
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2007
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Robert Sayre <sayrer@gmail.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# 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 *****
+
+DEPTH          = ../../..
+topsrcdir      = @top_srcdir@
+srcdir         = @srcdir@
+VPATH          = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE         = dom
+LIBRARY_NAME   = json_s
+LIBXUL_LIBRARY = 1
+
+REQUIRES       = xpcom         \
+                 string        \
+                 content       \
+                 caps          \
+                 js            \
+                 locale        \
+                 layout        \
+                 necko         \
+                 pref          \
+                 uconv         \
+                 unicharutil   \
+                 widget        \
+                 xpconnect     \
+                 $(NULL)
+
+CPPSRCS =                      \
+       nsJSON.cpp              \
+       $(NULL)
+
+FORCE_STATIC_LIB = 1
+
+LOCAL_INCLUDES = \
+		-I$(srcdir)/../base \
+		-I$(topsrcdir)/content/events/src
+
+DEFINES += -D_IMPL_NS_LAYOUT
+
+ifdef ENABLE_TESTS
+  DIRS += test
+endif
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/src/json/nsJSON.cpp
@@ -0,0 +1,1245 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Dave Camp <dcamp@mozilla.com>
+ *   Robert Sayre <sayrer@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * 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 "jsapi.h"
+#include "jsdtoa.h"
+#include "jsnum.h"
+#include "jsbool.h"
+#include "jsarena.h"
+#include "jscntxt.h"
+#include "jsinterp.h"
+#include "jsiter.h"
+#include "jstypes.h"
+#include "nsIServiceManager.h"
+#include "nsJSON.h"
+#include "nsIXPConnect.h"
+#include "nsIXPCScriptable.h"
+#include "nsStreamUtils.h"
+#include "nsIInputStream.h"
+#include "nsStringStream.h"
+#include "nsICharsetConverterManager.h"
+#include "nsXPCOMStrings.h"
+#include "nsNetUtil.h"
+#include "nsContentUtils.h"
+#include "nsCRTGlue.h"
+#include "nsAutoPtr.h"
+
+static const char kXPConnectServiceCID[] = "@mozilla.org/js/xpc/XPConnect;1";
+
+NS_INTERFACE_MAP_BEGIN(nsJSON)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIJSON)
+  NS_INTERFACE_MAP_ENTRY(nsIJSON)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(nsJSON)
+NS_IMPL_RELEASE(nsJSON)
+
+nsJSON::nsJSON()
+{
+}
+
+nsJSON::~nsJSON()
+{
+}
+
+//
+// AString encode(in JSObject value, [optional] in JSObject whitelist);
+//
+NS_IMETHODIMP
+nsJSON::Encode(nsAString &aJSON)
+{
+  // This function should only be called from JS.
+  nsresult rv;
+
+  nsAutoPtr<nsJSONWriter> writer(new nsJSONWriter());
+  if (!writer)
+    return NS_ERROR_OUT_OF_MEMORY;
+  
+  rv = EncodeInternal(writer);
+
+  // FIXME: bug 408838. Get exception types sorted out
+  if (NS_SUCCEEDED(rv) || rv == NS_ERROR_INVALID_ARG) {
+    rv = NS_OK;
+    // if we didn't consume anything, it's not JSON, so return null
+    if (writer->mBuffer.IsEmpty()) {
+      aJSON.Truncate();
+      aJSON.SetIsVoid(PR_TRUE);
+    } else {
+      aJSON.Append(writer->mBuffer);
+    }
+  }
+
+  return rv;
+}
+
+static const char UTF8BOM[] = "\xEF\xBB\xBF";
+static const char UTF16LEBOM[] = "\xFF\xFE";
+static const char UTF16BEBOM[] = "\xFE\xFF";
+static const char UTF32LEBOM[] = "\xFF\xFE\0\0";
+static const char UTF32BEBOM[] = "\0\0\xFE\xFF";
+
+static nsresult CheckCharset(const char* aCharset)
+{
+  // Check that the charset is permissible
+  if (!(strcmp(aCharset, "UTF-8") == 0 ||
+        strcmp(aCharset, "UTF-16LE") == 0 ||
+        strcmp(aCharset, "UTF-16BE") == 0 ||
+        strcmp(aCharset, "UTF-32LE") == 0 ||
+        strcmp(aCharset, "UTF-32BE") == 0)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  return NS_OK;
+}
+
+//
+// void EncodeToStream(in nsIOutputStream stream
+//                     /* in JSObject value,
+//                     /* [optional] in JSObject whitelist */);
+//
+NS_IMETHODIMP
+nsJSON::EncodeToStream(nsIOutputStream *aStream,
+                       const char* aCharset,
+                       const PRBool aWriteBOM)
+{
+  // This function should only be called from JS.
+  NS_ENSURE_ARG(aStream);
+  nsresult rv;
+
+  rv = CheckCharset(aCharset);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Check to see if we have a buffered stream
+  nsCOMPtr<nsIOutputStream> bufferedStream;
+  // FIXME: bug 408514.
+  // NS_OutputStreamIsBuffered(aStream) asserts on file streams...
+  //if (!NS_OutputStreamIsBuffered(aStream)) {
+    rv = NS_NewBufferedOutputStream(getter_AddRefs(bufferedStream),
+                                    aStream, 4096);
+    NS_ENSURE_SUCCESS(rv, rv);
+  //  aStream = bufferedStream;
+  //}
+
+  PRUint32 ignored;
+  if (aWriteBOM) {
+    if (strcmp(aCharset, "UTF-8") == 0)
+      rv = aStream->Write(UTF8BOM, 3, &ignored);
+    else if (strcmp(aCharset, "UTF-16LE") == 0)
+      rv = aStream->Write(UTF16LEBOM, 2, &ignored);
+    else if (strcmp(aCharset, "UTF-16BE") == 0)
+      rv = aStream->Write(UTF16BEBOM, 2, &ignored);
+    else if (strcmp(aCharset, "UTF-32LE") == 0)
+      rv = aStream->Write(UTF32LEBOM, 4, &ignored);
+    else if (strcmp(aCharset, "UTF-32BE") == 0)
+      rv = aStream->Write(UTF32BEBOM, 4, &ignored);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  nsAutoPtr<nsJSONWriter> writer(new nsJSONWriter(bufferedStream));
+  if (!writer)
+    return NS_ERROR_OUT_OF_MEMORY;
+  rv = writer->SetCharset(aCharset);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = EncodeInternal(writer);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = bufferedStream->Flush();
+
+  return rv;
+}
+
+nsresult
+nsJSON::EncodeInternal(nsJSONWriter *writer)
+{
+  nsresult rv;
+  nsIXPConnect *xpc = nsContentUtils::XPConnect();
+  if (!xpc)
+    return NS_ERROR_FAILURE;
+
+  nsCOMPtr<nsIXPCNativeCallContext> cc;
+  rv = xpc->GetCurrentNativeCallContext(getter_AddRefs(cc));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  JSContext *cx = nsnull;
+  rv = cc->GetJSContext(&cx);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  JSAutoRequest ar(cx);
+
+  PRUint32 argc = 0;
+  rv = cc->GetArgc(&argc);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Now fish for the JS arguments. If it's a call to encode, we'll
+  // want the first two arguments. If it's a call to encodeToStream,
+  // we'll want the fourth and fifth;
+  PRUint32 firstArg = writer->mStream ? 3 : 0;
+
+  // Get the object we're going to serialize.
+  JSObject *inputObj = nsnull;
+  jsval *argv = nsnull;
+  rv = cc->GetArgvPtr(&argv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (argc <= firstArg ||
+      !(JSVAL_IS_OBJECT(argv[firstArg]) &&
+        (inputObj = JSVAL_TO_OBJECT(argv[firstArg])))) {
+    // return if it's not something we can deal with
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  JSObject *whitelist = nsnull;
+
+  // If there's a second argument here, it should be an array.
+  if (argc >= firstArg + 2 &&
+      !(JSVAL_IS_OBJECT(argv[firstArg + 1]) &&
+        (whitelist = JSVAL_TO_OBJECT(argv[firstArg + 1])) &&
+        JS_IsArrayObject(cx, whitelist))) {
+     whitelist = nsnull; // bogus whitelists are ignored
+  }
+
+  jsval *vp = &argv[firstArg];
+
+  JSBool ok = ToJSON(cx, vp);
+  JSType type;
+  if (!(ok && !JSVAL_IS_PRIMITIVE(*vp) &&
+        (type = JS_TypeOfValue(cx, *vp)) != JSTYPE_FUNCTION &&
+        type != JSTYPE_XML)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  return EncodeObject(cx, vp, writer, whitelist, 0);
+}
+
+// N.B: vp must be rooted.
+nsresult
+nsJSON::EncodeObject(JSContext *cx, jsval *vp, nsJSONWriter *writer,
+                     JSObject *whitelist, PRUint32 depth)
+{
+  NS_ENSURE_ARG(vp);
+  NS_ENSURE_ARG(writer);
+
+  if (depth > JSON_MAX_DEPTH) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsresult rv;
+  JSObject *obj = JSVAL_TO_OBJECT(*vp);
+  JSBool isArray = JS_IsArrayObject(cx, obj);
+  PRUnichar output = PRUnichar(isArray ? '[' : '{');
+  rv = writer->Write(&output, 1);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  JSBool ok = JS_TRUE;
+
+  ok = js_ValueToIterator(cx, JSITER_ENUMERATE, vp);
+  if (!ok)
+    return NS_ERROR_FAILURE;
+
+  JSObject *iterObj = JSVAL_TO_OBJECT(*vp);
+
+  jsval outputValue = JSVAL_VOID;
+  JSAutoTempValueRooter tvr(cx, 1, &outputValue);
+
+  jsval key;
+  PRBool memberWritten = PR_FALSE;
+  do {
+    outputValue = JSVAL_VOID;
+    ok = js_CallIteratorNext(cx, iterObj, &key);
+
+    if (!ok)
+      break;
+
+    if (key == JSVAL_HOLE)
+      break;
+
+    JSString *ks;
+    if (JSVAL_IS_STRING(key)) {
+      ks = JSVAL_TO_STRING(key);
+    } else {
+      ks = JS_ValueToString(cx, key);
+      if (!ks) {
+        ok = JS_FALSE;
+        break;
+      }
+    }
+
+    ok = JS_GetUCProperty(cx, obj, JS_GetStringChars(ks),
+                          JS_GetStringLength(ks), &outputValue);
+    if (!ok)
+      break;
+
+    // if this is an array, holes are transmitted as null
+    if (isArray && outputValue == JSVAL_VOID) {
+      outputValue = JSVAL_NULL;
+    } else if (JSVAL_IS_OBJECT(outputValue)) {
+      ok = ToJSON(cx, &outputValue);
+      if (!ok)
+        break;
+    }
+    
+    // elide undefined values
+    if (outputValue == JSVAL_VOID)
+      continue;
+
+    // output a comma unless this is the first member to write
+    if (memberWritten) {
+      output = PRUnichar(',');
+      rv = writer->Write(&output, 1);
+    }
+    memberWritten = PR_TRUE;
+    
+    JSType type = JS_TypeOfValue(cx, outputValue);
+
+    // Can't encode these types, so drop them
+    if (type == JSTYPE_FUNCTION || type == JSTYPE_XML)
+      break;
+ 
+    // Be careful below, this string is weakly rooted.
+    JSString *s;
+ 
+    // If this isn't an array, we need to output a key
+    if (!isArray) {
+      nsAutoString keyOutput;
+      s = JS_ValueToString(cx, key);
+      if (!s) {
+        ok = JS_FALSE;
+        break;
+      }
+
+      rv = writer->WriteString((PRUnichar*)JS_GetStringChars(s),
+                               JS_GetStringLength(s));
+      if (NS_FAILED(rv))
+        break;
+      output = PRUnichar(':');
+      rv = writer->Write(&output, 1);
+      if (NS_FAILED(rv))
+        break;
+    }
+
+    if (!JSVAL_IS_PRIMITIVE(outputValue)) {
+      // recurse
+      rv = EncodeObject(cx, &outputValue, writer, whitelist, depth + 1);
+      if (NS_FAILED(rv))
+        break;
+    } else {
+      nsAutoString valueOutput;
+      s = JS_ValueToString(cx, outputValue);
+      if (!s) {
+        ok = JS_FALSE;
+        break;
+      }
+
+      if (type == JSTYPE_STRING) {
+        rv = writer->WriteString((PRUnichar*)JS_GetStringChars(s),
+                                 JS_GetStringLength(s));
+        continue;
+      }
+
+      if (type == JSTYPE_NUMBER) {
+        if (JSVAL_IS_DOUBLE(outputValue)) {
+          jsdouble d = *JSVAL_TO_DOUBLE(outputValue);
+          if (!JSDOUBLE_IS_FINITE(d))
+            valueOutput.Append(NS_LITERAL_STRING("null"));
+          else
+            valueOutput.Append((PRUnichar*)JS_GetStringChars(s));
+        } else {
+          valueOutput.Append((PRUnichar*)JS_GetStringChars(s));
+        }
+      } else if (type == JSTYPE_BOOLEAN) {
+        valueOutput.Append((PRUnichar*)JS_GetStringChars(s));
+      } else if (JSVAL_IS_NULL(outputValue)) {
+        valueOutput.Append(NS_LITERAL_STRING("null"));
+      } else {
+        rv = NS_ERROR_FAILURE; // encoding error
+        break;
+      }
+
+      rv = writer->Write(valueOutput.get(), valueOutput.Length());
+    }
+
+  } while (NS_SUCCEEDED(rv));
+
+  // Always close the iterator, but make sure not to stomp on OK
+  ok &= js_CloseIterator(cx, *vp);
+
+  if (!ok)
+    rv = NS_ERROR_FAILURE; // encoding error or propagate? FIXME: Bug 408838.
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  output = PRUnichar(isArray ? ']' : '}');
+  rv = writer->Write(&output, 1);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return rv;
+}
+
+JSBool
+nsJSON::ToJSON(JSContext *cx, jsval *vp)
+{
+  // Now we check to see whether the return value implements toJSON()
+  JSBool ok = JS_TRUE;
+  char *toJSON = "toJSON";
+
+  if (!JSVAL_IS_PRIMITIVE(*vp)) {
+    JSObject *obj = JSVAL_TO_OBJECT(*vp);
+    jsval toJSONVal = nsnull;
+    ok = JS_GetProperty(cx, obj, toJSON, &toJSONVal);
+    if (ok && JS_TypeOfValue(cx, toJSONVal) == JSTYPE_FUNCTION) {
+      ok = JS_CallFunctionValue(cx, obj, toJSONVal, 0, nsnull, vp);
+    }
+  }
+
+  return ok;
+}
+
+nsJSONWriter::nsJSONWriter() : mStream(nsnull), mEncoder(nsnull)
+{
+}
+
+nsJSONWriter::nsJSONWriter(nsIOutputStream *aStream) : mStream(aStream),
+                                                       mEncoder(nsnull)
+{
+}
+
+nsJSONWriter::~nsJSONWriter()
+{
+}
+
+nsresult
+nsJSONWriter::SetCharset(const char* aCharset)
+{
+  nsresult rv = NS_OK;
+  if (mStream) {
+    nsCOMPtr<nsICharsetConverterManager> ccm =
+      do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = ccm->GetUnicodeEncoder(aCharset, getter_AddRefs(mEncoder));
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = mEncoder->SetOutputErrorBehavior(nsIUnicodeEncoder::kOnError_Signal,
+                                          nsnull, nsnull);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return rv;
+}
+
+static const PRUnichar quote = PRUnichar('"');
+static const PRUnichar backslash = PRUnichar('\\');
+static const PRUnichar unicodeEscape[] = {'\\', 'u', '0', '0', '\0'};
+
+nsresult
+nsJSONWriter::WriteString(const PRUnichar *aBuffer, PRUint32 aLength)
+{
+  nsresult rv;
+  rv = Write(&quote, 1);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  PRUint32 mark = 0;
+  PRUint32 i;
+  for (i = 0; i < aLength; ++i) {
+    if (aBuffer[i] == quote || aBuffer[i] == backslash) {
+      rv = Write(&aBuffer[mark], i - mark);
+      NS_ENSURE_SUCCESS(rv, rv);
+      rv = Write(&backslash, 1);
+      NS_ENSURE_SUCCESS(rv, rv);
+      rv = Write(&aBuffer[i], 1);
+      NS_ENSURE_SUCCESS(rv, rv);
+      mark = i + 1;
+    } else if (aBuffer[i] <= 31 || aBuffer[i] == 127) {
+      rv = Write(&aBuffer[mark], i - mark);
+      NS_ENSURE_SUCCESS(rv, rv);
+      nsAutoString unicode;
+      unicode.Append(unicodeEscape);
+      nsAutoString charCode;
+      charCode.AppendInt(aBuffer[i], 16);
+      if (charCode.Length() == 1)
+        unicode.Append('0');
+      unicode.Append(charCode);
+      rv = Write(unicode.get(), unicode.Length());
+      NS_ENSURE_SUCCESS(rv, rv);
+      mark = i + 1;
+    }
+  }
+
+  if (mark < aLength) {
+    rv = Write(&aBuffer[mark], aLength - mark);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  rv = Write(&quote, 1);
+  NS_ENSURE_SUCCESS(rv, rv);
+  return rv;
+}
+
+nsresult
+nsJSONWriter::Write(const PRUnichar *aBuffer, PRUint32 aLength)
+{
+  nsresult rv = NS_OK;
+  if (mStream) {
+    rv = WriteToStream(mStream, mEncoder, aBuffer, aLength);
+  } else {
+    mBuffer.Append(aBuffer, aLength);
+  }
+
+  return rv;
+}
+
+nsresult
+nsJSONWriter::WriteToStream(nsIOutputStream *aStream,
+                            nsIUnicodeEncoder *encoder,
+                            const PRUnichar *aBuffer,
+                            PRUint32 aLength)
+{
+  nsresult rv;
+  PRInt32 srcLength = aLength;
+  PRUint32 bytesWritten;
+
+  // The bytes written to the stream might differ from the PRUnichar size
+  PRInt32 aDestLength;
+  rv = encoder->GetMaxLength(aBuffer, srcLength, &aDestLength);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // create the buffer we need
+  char* destBuf = (char *) NS_Alloc(aDestLength);
+  if (!destBuf)
+    return NS_ERROR_OUT_OF_MEMORY;
+
+  rv = encoder->Convert(aBuffer, &srcLength, destBuf, &aDestLength);
+  if (NS_SUCCEEDED(rv))
+    rv = aStream->Write(destBuf, aDestLength, &bytesWritten);
+
+  NS_Free(destBuf);
+
+  return rv;
+}
+
+NS_IMETHODIMP
+nsJSON::Decode(const nsAString& json)
+{
+  const PRUnichar *data;
+  PRUint32 len = NS_StringGetData(json, &data);
+  nsCOMPtr<nsIInputStream> stream;
+  nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream),
+                                      (const char*) data,
+                                      len * sizeof(PRUnichar),
+                                      NS_ASSIGNMENT_DEPEND);
+  NS_ENSURE_SUCCESS(rv, rv);
+  return DecodeInternal(stream, len, PR_FALSE);
+}
+
+NS_IMETHODIMP
+nsJSON::DecodeFromStream(nsIInputStream *aStream, PRInt32 aContentLength)
+{
+  return DecodeInternal(aStream, aContentLength, PR_TRUE);
+}
+
+nsresult
+nsJSON::DecodeInternal(nsIInputStream *aStream,
+                       PRInt32 aContentLength,
+                       PRBool aNeedsConverter)
+{
+  nsresult rv;
+  nsIXPConnect *xpc = nsContentUtils::XPConnect();
+  if (!xpc)
+    return NS_ERROR_FAILURE;
+
+  nsCOMPtr<nsIXPCNativeCallContext> cc;
+  rv = xpc->GetCurrentNativeCallContext(getter_AddRefs(cc));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  jsval *retvalPtr;
+  rv = cc->GetRetValPtr(&retvalPtr);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  JSContext *cx = nsnull;
+  rv = cc->GetJSContext(&cx);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  JSAutoRequest ar(cx);
+
+  // Consume the stream
+  nsCOMPtr<nsIChannel> jsonChannel;
+  nsCOMPtr<nsIURI> uri;
+  NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("about:blank"), 0, 0 );
+  if (!uri)
+    return NS_ERROR_OUT_OF_MEMORY;
+  rv = NS_NewInputStreamChannel(getter_AddRefs(jsonChannel), uri, aStream,
+                                NS_LITERAL_CSTRING("application/json"));
+  if (!jsonChannel || NS_FAILED(rv))
+    return NS_ERROR_FAILURE;
+
+  nsRefPtr<nsJSONListener>
+    jsonListener(new nsJSONListener(cx, retvalPtr, aNeedsConverter));
+
+  if (!jsonListener)
+    return NS_ERROR_OUT_OF_MEMORY;
+
+  //XXX this stream pattern should be consolidated in netwerk
+  rv = jsonListener->OnStartRequest(jsonChannel, nsnull);
+  if (NS_FAILED(rv)) {
+    jsonChannel->Cancel(rv);
+    return rv;
+  }
+
+  nsresult status;
+  jsonChannel->GetStatus(&status);
+  PRUint32 offset = 0;
+  while (NS_SUCCEEDED(status)) {
+    PRUint32 available;
+    rv = aStream->Available(&available);
+    if (rv == NS_BASE_STREAM_CLOSED) {
+      rv = NS_OK;
+      break;
+    }
+    if (NS_FAILED(rv)) {
+      jsonChannel->Cancel(rv);
+      break;
+    }
+    if (!available)
+      break; // blocking input stream has none available when done
+
+    rv = jsonListener->OnDataAvailable(jsonChannel, nsnull,
+                                       aStream, offset, available);
+    if (NS_FAILED(rv)) {
+      jsonChannel->Cancel(rv);
+      break;
+    }
+
+    offset += available;
+    jsonChannel->GetStatus(&status);
+  }
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = jsonListener->OnStopRequest(jsonChannel, nsnull, status);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = cc->SetReturnValueWasSet(PR_TRUE);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+NS_NewJSON(nsISupports* aOuter, REFNSIID aIID, void** aResult)
+{
+  nsJSON* json = new nsJSON();
+  if (!json)
+    return NS_ERROR_OUT_OF_MEMORY;
+
+  NS_ADDREF(json);
+  *aResult = json;
+
+  return NS_OK;
+}
+
+
+JS_STATIC_DLL_CALLBACK(void)
+trace_json_stack(JSTracer *trc, JSTempValueRooter *tvr)
+{
+  nsJSONObjectStack *tmp = static_cast<nsJSONObjectStack *>(tvr);
+
+  for (PRUint32 i = 0; i < tmp->Length(); ++i) {
+    JS_CALL_OBJECT_TRACER(trc, tmp->ElementAt(i),
+                          "JSON decoder stack member");
+  }
+}
+
+nsJSONListener::nsJSONListener(JSContext *cx, jsval *rootVal,
+                               PRBool needsConverter)
+  : mLineNum(0),
+    mColumn(0),
+    mHexChar(0),
+    mNumHex(0),
+    mCx(cx),
+    mRootVal(rootVal),
+    mNeedsConverter(needsConverter),
+    mStatep(mStateStack)
+{
+  NS_ASSERTION(mCx, "Must have a JSContext");
+  *mStatep = JSON_PARSE_STATE_INIT;
+  JS_PUSH_TEMP_ROOT_TRACE(cx, trace_json_stack, &mObjectStack);
+}
+
+nsJSONListener::~nsJSONListener()
+{
+  JS_POP_TEMP_ROOT(mCx, &mObjectStack);
+}
+
+NS_INTERFACE_MAP_BEGIN(nsJSONListener)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsJSONListener)
+  NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+  NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(nsJSONListener)
+NS_IMPL_RELEASE(nsJSONListener)
+
+NS_IMETHODIMP
+nsJSONListener::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
+{
+  mHexChar = 0;
+  mNumHex = 0;
+  mSniffBuffer.Truncate();
+  mDecoder = nsnull;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJSONListener::OnStopRequest(nsIRequest *aRequest, nsISupports *aContext,
+                              nsresult aStatusCode)
+{
+  nsresult rv;
+
+  // This can happen with short UTF-8 messages
+  if (!mSniffBuffer.IsEmpty()) {
+    rv = ProcessBytes(mSniffBuffer.get(), mSniffBuffer.Length());
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  if (!mObjectStack.IsEmpty() || *mStatep != JSON_PARSE_STATE_FINISHED)
+    return NS_ERROR_FAILURE;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJSONListener::OnDataAvailable(nsIRequest *aRequest, nsISupports *aContext,
+                                nsIInputStream *aStream,
+                                PRUint32 aOffset, PRUint32 aLength)
+{
+  PRUint32 contentLength;
+  aStream->Available(&contentLength);
+  nsresult rv;
+
+  if (mNeedsConverter && mSniffBuffer.Length() < 4) {
+    PRUint32 readCount = (aLength < 4) ? aLength : 4;
+    rv = NS_ConsumeStream(aStream, readCount, mSniffBuffer);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (mSniffBuffer.Length() < 4)
+      return NS_OK;
+  }
+  
+  char buffer[JSON_PARSER_BUFSIZE];
+  unsigned long bytesRemaining = aLength - mSniffBuffer.Length();
+  while (bytesRemaining) {
+    unsigned int bytesRead;
+    rv = aStream->Read(buffer,
+                       PR_MIN(sizeof(buffer), bytesRemaining),
+                       &bytesRead);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = ProcessBytes(buffer, bytesRead);
+    NS_ENSURE_SUCCESS(rv, rv);
+    bytesRemaining -= bytesRead;
+  }
+
+  return rv;
+}
+
+nsresult
+nsJSONListener::ProcessBytes(const char* aBuffer, PRUint32 aByteLength)
+{
+  nsresult rv;
+  // Check for BOM, or sniff charset
+  nsCAutoString charset;
+  if (mNeedsConverter && !mDecoder) {
+    if (!nsContentUtils::CheckForBOM((const unsigned char*) mSniffBuffer.get(),
+                                      mSniffBuffer.Length(), charset)) {
+      // OK, found no BOM, sniff the first character to see what this is
+      // See section 3 of RFC4627 for details on why this works.
+      const char *buffer = mSniffBuffer.get();
+      if (mSniffBuffer.Length() >= 4) {
+        if (buffer[0] == 0x00 && buffer[1] == 0x00 &&
+            buffer[2] == 0x00 && buffer[3] != 0x00) {
+          charset = "UTF-32BE";
+        } else if (buffer[0] == 0x00 && buffer[1] != 0x00 &&
+                   buffer[2] == 0x00 && buffer[3] != 0x00) {
+          charset = "UTF-16BE";
+        } else if (buffer[0] != 0x00 && buffer[1] == 0x00 &&
+                   buffer[2] == 0x00 && buffer[3] == 0x00) {
+          charset = "UTF-32LE";
+        } else if (buffer[0] != 0x00 && buffer[1] == 0x00 &&
+                   buffer[2] != 0x00 && buffer[3] == 0x00) {
+          charset = "UTF-16LE";
+        } else if (buffer[0] != 0x00 && buffer[1] != 0x00 &&
+                   buffer[2] != 0x00 && buffer[3] != 0x00) {
+          charset = "UTF-8";
+        }
+      }
+    }
+
+    // We should have a unicode charset by now
+    rv = CheckCharset(charset.get());
+    NS_ENSURE_SUCCESS(rv, rv);
+    nsCOMPtr<nsICharsetConverterManager> ccm =
+        do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = ccm->GetUnicodeDecoderRaw(charset.get(), getter_AddRefs(mDecoder));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // consume the sniffed bytes
+    rv = ConsumeConverted(mSniffBuffer.get(), mSniffBuffer.Length());
+    NS_ENSURE_SUCCESS(rv, rv);
+    mSniffBuffer.Truncate();
+  }
+
+  if (mNeedsConverter) {
+    rv = ConsumeConverted(aBuffer, aByteLength);
+  } else {
+    PRUint32 unichars = aByteLength / sizeof(PRUnichar);
+    rv = Consume((PRUnichar *) aBuffer, unichars);
+  }
+
+  return rv;
+}
+
+nsresult
+nsJSONListener::ConsumeConverted(const char* aBuffer, PRUint32 aByteLength)
+{
+  nsresult rv;
+  PRInt32 unicharLength = 0;
+  PRInt32 srcLen = aByteLength;
+
+  rv = mDecoder->GetMaxLength(aBuffer, srcLen, &unicharLength);
+  NS_ENSURE_SUCCESS(rv, rv);
+  nsAutoArrayPtr<PRUnichar> ustr(new PRUnichar[unicharLength]);
+  NS_ENSURE_TRUE(ustr, NS_ERROR_OUT_OF_MEMORY);
+  rv = mDecoder->Convert(aBuffer, &srcLen, ustr, &unicharLength);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = Consume(ustr.get(), unicharLength);
+
+  return rv;
+}
+
+nsresult
+nsJSONListener::PopState()
+{
+  mStatep--;
+  if (mStatep < mStateStack) {
+    mStatep = mStateStack;
+    return NS_ERROR_FAILURE;
+  } else if (*mStatep == JSON_PARSE_STATE_INIT) {
+    *mStatep = JSON_PARSE_STATE_FINISHED;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+nsJSONListener::PushState(JSONParserState state)
+{
+  if (*mStatep == JSON_PARSE_STATE_FINISHED)
+    return NS_ERROR_FAILURE; // extra input
+
+  mStatep++;
+  if ((uint32)(mStatep - mStateStack) >= JS_ARRAY_LENGTH(mStateStack))
+    return NS_ERROR_FAILURE; // too deep
+
+  *mStatep = state;
+
+  return NS_OK;
+}
+
+nsresult
+nsJSONListener::Consume(const PRUnichar *data, PRUint32 len)
+{
+  nsresult rv;
+  nsString numchars = NS_LITERAL_STRING("-+0123456789eE.");
+  PRUint32 i;
+
+  if (*mStatep == JSON_PARSE_STATE_INIT) {
+    PushState(JSON_PARSE_STATE_VALUE);
+  }
+
+  for (i = 0; i < len; i++) {
+    PRUnichar c = data[i];
+    if (c == '\n') {
+      mLineNum++;
+      mColumn = 0;
+    } else {
+      mColumn++;
+    }
+
+    switch (*mStatep) {
+      case JSON_PARSE_STATE_VALUE :
+        if (c == '{') {
+          *mStatep = JSON_PARSE_STATE_OBJECT;
+          rv = this->OpenObject();
+          NS_ENSURE_SUCCESS(rv, rv);
+          rv = PushState(JSON_PARSE_STATE_OBJECT_PAIR);
+          NS_ENSURE_SUCCESS(rv, rv);
+        } else if (c == '[') {
+          *mStatep = JSON_PARSE_STATE_ARRAY;
+          rv = this->OpenArray();
+          NS_ENSURE_SUCCESS(rv, rv);
+          rv = PushState(JSON_PARSE_STATE_VALUE);
+          NS_ENSURE_SUCCESS(rv, rv);
+        } else if (c == ']') {
+          // empty array
+          rv = PopState();
+          NS_ENSURE_SUCCESS(rv, rv);
+          if (*mStatep != JSON_PARSE_STATE_ARRAY) {
+            return NS_ERROR_FAILURE; // unexpected char
+          }
+          rv = this->CloseArray();
+          NS_ENSURE_SUCCESS(rv, rv);
+          rv = PopState();
+          NS_ENSURE_SUCCESS(rv, rv);
+        } else if (c == '}') {
+          // we should only find these in OBJECT_KEY state
+          return NS_ERROR_FAILURE; // unexpected failure
+        } else if (c == '"') {
+          *mStatep = JSON_PARSE_STATE_STRING;
+        } else if (numchars.FindChar(c) >= 0) {
+          *mStatep = JSON_PARSE_STATE_NUMBER;
+          mStringBuffer.Append(c);
+        } else if (NS_IsAsciiAlpha(c)) {
+          *mStatep = JSON_PARSE_STATE_KEYWORD;
+          mStringBuffer.Append(c);
+        } else if (!NS_IsAsciiWhitespace(c)) {
+          return NS_ERROR_FAILURE; // unexpected
+        }
+        break;
+      case JSON_PARSE_STATE_OBJECT :
+        if (c == '}') {
+          rv = this->CloseObject();
+          NS_ENSURE_SUCCESS(rv, rv);
+          rv = PopState();
+          NS_ENSURE_SUCCESS(rv, rv);
+        } else if (c == ',') {
+          rv = PushState(JSON_PARSE_STATE_OBJECT_PAIR);
+          NS_ENSURE_SUCCESS(rv, rv);
+        } else if (c == ']') {
+          return NS_ERROR_FAILURE; // unexpected
+        } else if (!NS_IsAsciiWhitespace(c)) {
+          return NS_ERROR_FAILURE; // unexpected
+        }
+        break;
+      case JSON_PARSE_STATE_ARRAY :
+        if (c == ']') {
+          rv = this->CloseArray();
+          NS_ENSURE_SUCCESS(rv, rv);
+          rv = PopState();
+          NS_ENSURE_SUCCESS(rv, rv);
+        } else if (c == ',') {
+          rv = PushState(JSON_PARSE_STATE_VALUE);
+          NS_ENSURE_SUCCESS(rv, rv);
+        } else if (!NS_IsAsciiWhitespace(c)) {
+          return NS_ERROR_FAILURE; // unexpected
+        }
+        break;
+      case JSON_PARSE_STATE_OBJECT_PAIR :
+        if (c == '"') {
+          // we want to be waiting for a : when the string has been read
+          *mStatep = JSON_PARSE_STATE_OBJECT_IN_PAIR;
+          PushState(JSON_PARSE_STATE_STRING);
+        } else if (c == '}') {
+          rv = this->CloseObject();
+          NS_ENSURE_SUCCESS(rv, rv);
+          // pop off the object_pair state
+          rv = PopState();
+          NS_ENSURE_SUCCESS(rv, rv);
+          // pop off the object state
+          rv = PopState();
+          NS_ENSURE_SUCCESS(rv, rv);
+        } else if (c == ']' || !NS_IsAsciiWhitespace(c)) {
+          return NS_ERROR_FAILURE; // unexpected
+        }
+        break;
+      case JSON_PARSE_STATE_OBJECT_IN_PAIR:
+        if (c == ':') {
+          *mStatep = JSON_PARSE_STATE_VALUE;
+        } else if (!NS_IsAsciiWhitespace(c)) {
+          return NS_ERROR_FAILURE; // unexpected
+        }
+        break;
+      case JSON_PARSE_STATE_STRING:
+        if (c == '"') {
+          rv = PopState();
+          NS_ENSURE_SUCCESS(rv, rv);
+          rv = HandleString();
+          NS_ENSURE_SUCCESS(rv, rv);
+        } else if (c == '\\') {
+          *mStatep = JSON_PARSE_STATE_STRING_ESCAPE;
+        } else {
+          mStringBuffer.Append(c);
+        }
+        break;
+      case JSON_PARSE_STATE_STRING_ESCAPE:
+        switch(c) {
+          case '"':
+          case '\\':
+          case '/':
+            break;
+          case 'b' : c = '\b'; break;
+          case 'f' : c = '\f'; break;
+          case 'n' : c = '\n'; break;
+          case 'r' : c = '\r'; break;
+          case 't' : c = '\t'; break;
+          default :
+            if (c == 'u') {
+              mNumHex = 0;
+              mHexChar = 0;
+              *mStatep = JSON_PARSE_STATE_STRING_HEX;
+              continue;
+            } else {
+              return NS_ERROR_FAILURE; // unexpected
+            }
+        }
+
+        mStringBuffer.Append(c);
+        *mStatep = JSON_PARSE_STATE_STRING;
+        break;
+      case JSON_PARSE_STATE_STRING_HEX:
+        if (('0' <= c) && (c <= '9')) {
+          mHexChar = (mHexChar << 4) | (c - '0');
+        } else if (('a' <= c) && (c <= 'f')) {
+          mHexChar = (mHexChar << 4) | (c - 'a' + 0x0a);
+        } else if (('A' <= c) && (c <= 'F')) {
+          mHexChar = (mHexChar << 4) | (c - 'A' + 0x0a);
+        } else {
+          return NS_ERROR_FAILURE; // unexpected
+        }
+
+        if (++(mNumHex) == 4) {
+          mStringBuffer.Append(mHexChar);
+          mHexChar = 0;
+          mNumHex = 0;
+          *mStatep = JSON_PARSE_STATE_STRING;
+        }
+        break;
+      case JSON_PARSE_STATE_KEYWORD:
+        if (NS_IsAsciiAlpha(c)) {
+          mStringBuffer.Append(c);
+        } else {
+          // this character isn't part of the keyword, process it again
+          i--;
+          rv = PopState();
+          NS_ENSURE_SUCCESS(rv, rv);
+          rv = HandleKeyword();
+          NS_ENSURE_SUCCESS(rv, rv);
+        }
+        break;
+      case JSON_PARSE_STATE_NUMBER:
+        if (numchars.FindChar(c) >= 0) {
+          mStringBuffer.Append(c);
+        } else {
+          // this character isn't part of the number, process it again
+          i--;
+          rv = PopState();
+          NS_ENSURE_SUCCESS(rv, rv);
+          rv = HandleNumber();
+          NS_ENSURE_SUCCESS(rv, rv);
+        }
+        break;
+      case JSON_PARSE_STATE_FINISHED:
+        if (!NS_IsAsciiWhitespace(c)) {
+          return NS_ERROR_FAILURE; // extra input
+        }
+        break;
+      default:
+        NS_NOTREACHED("Invalid JSON parser state");
+      }
+    }
+
+    return NS_OK;
+}
+
+nsresult
+nsJSONListener::PushValue(JSObject *aParent, jsval aValue)
+{
+  JSAutoTempValueRooter tvr(mCx, 1, &aValue);
+  
+  JSBool ok;
+  if (JS_IsArrayObject(mCx, aParent)) {
+    jsuint len;
+    ok = JS_GetArrayLength(mCx, aParent, &len);
+    if (ok) {
+      ok = JS_SetElement(mCx, aParent, len, &aValue);
+    }
+  } else {
+    ok = JS_DefineUCProperty(mCx, aParent, (jschar *) mObjectKey.get(),
+                             mObjectKey.Length(), aValue,
+                             NULL, NULL, JSPROP_ENUMERATE);
+  }
+
+  return ok ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult
+nsJSONListener::PushObject(JSObject *aObj)
+{
+  if (mObjectStack.Length() >= JSON_MAX_DEPTH)
+    return NS_ERROR_FAILURE; // decoding error
+
+  // Check if this is the root object
+  if (mObjectStack.IsEmpty()) {
+    *mRootVal = OBJECT_TO_JSVAL(aObj);
+    if (!mObjectStack.AppendElement(aObj))
+      return NS_ERROR_OUT_OF_MEMORY;
+    return NS_OK;
+  }
+
+  nsresult rv;
+  JSObject *parent = mObjectStack.ElementAt(mObjectStack.Length() - 1);
+  rv = PushValue(parent, OBJECT_TO_JSVAL(aObj));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!mObjectStack.AppendElement(aObj))
+    return NS_ERROR_OUT_OF_MEMORY;
+
+  return rv;
+}
+
+nsresult
+nsJSONListener::OpenObject()
+{
+  if (*mStatep != JSON_PARSE_STATE_VALUE &&
+      *mStatep != JSON_PARSE_STATE_OBJECT) {
+    return NS_ERROR_FAILURE;
+  }
+
+  JSObject *obj = JS_NewObject(mCx, NULL, NULL, NULL);
+  if (!obj)
+    return NS_ERROR_OUT_OF_MEMORY;
+
+  return PushObject(obj);
+}
+
+nsresult
+nsJSONListener::OpenArray()
+{
+  if (*mStatep != JSON_PARSE_STATE_VALUE &&
+      *mStatep != JSON_PARSE_STATE_ARRAY) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // Add an array to an existing array or object
+  JSObject *arr = JS_NewArrayObject(mCx, 0, NULL);
+  if (!arr)
+    return NS_ERROR_OUT_OF_MEMORY;
+
+  return PushObject(arr);
+}
+
+nsresult
+nsJSONListener::CloseObject()
+{
+  if (!mObjectStack.SetLength(mObjectStack.Length() - 1))
+    return NS_ERROR_FAILURE;
+
+  return NS_OK;
+}
+
+nsresult
+nsJSONListener::CloseArray()
+{
+  return this->CloseObject();
+}
+
+nsresult
+nsJSONListener::HandleString()
+{
+  nsresult rv = NS_OK;
+  if (*mStatep == JSON_PARSE_STATE_OBJECT_IN_PAIR) {
+    mObjectKey = mStringBuffer;
+  } else {
+    JSObject *obj = mObjectStack.ElementAt(mObjectStack.Length() - 1);
+    JSString *str = JS_NewUCStringCopyN(mCx, (jschar *) mStringBuffer.get(),
+                                        mStringBuffer.Length());
+    if (!str)
+      return NS_ERROR_OUT_OF_MEMORY;
+    rv = PushValue(obj, STRING_TO_JSVAL(str));
+  }
+
+  mStringBuffer.Truncate();
+  return rv;
+}
+
+nsresult
+nsJSONListener::HandleNumber()
+{
+  nsresult rv;
+  JSObject *obj = mObjectStack.ElementAt(mObjectStack.Length() - 1);
+
+  char *estr;
+  int err;
+  double val = JS_strtod(NS_ConvertUTF16toUTF8(mStringBuffer).get(),
+                         &estr, &err);
+  if (err == JS_DTOA_ENOMEM) {
+    rv = NS_ERROR_OUT_OF_MEMORY;
+  } else if (err || *estr) {
+    rv = NS_ERROR_FAILURE; // decode error
+  } else {
+    // ok
+    jsval numVal;
+    if (JS_NewNumberValue(mCx, val, &numVal)) {
+      rv = PushValue(obj, numVal);
+    } else {
+      rv = NS_ERROR_FAILURE; // decode error
+    }
+  }
+
+  mStringBuffer.Truncate();
+
+  return rv;
+}
+
+nsresult
+nsJSONListener::HandleKeyword()
+{
+  JSObject *obj = mObjectStack.ElementAt(mObjectStack.Length() - 1);
+  jsval keyword;
+  if (mStringBuffer.Equals(NS_LITERAL_STRING("null"))) {
+    keyword = JSVAL_NULL;
+  } else if (mStringBuffer.Equals(NS_LITERAL_STRING("true"))) {
+    keyword = JSVAL_TRUE;
+  } else if (mStringBuffer.Equals(NS_LITERAL_STRING("false"))) {
+    keyword = JSVAL_FALSE;
+  } else {
+    return NS_ERROR_FAILURE;
+  }
+
+  mStringBuffer.Truncate();
+
+  return PushValue(obj, keyword);
+}
new file mode 100644
--- /dev/null
+++ b/dom/src/json/nsJSON.h
@@ -0,0 +1,164 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Robert Sayre <sayrer@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * 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 ***** */
+
+#ifndef nsJSON_h__
+#define nsJSON_h__
+
+#include "jsprvtd.h"
+#include "nsIJSON.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIOutputStream.h"
+#include "nsIUnicodeEncoder.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsIRequestObserver.h"
+#include "nsIStreamListener.h"
+#include "nsTArray.h"
+
+#define JSON_MAX_DEPTH  2048
+#define JSON_PARSER_BUFSIZE 1024
+class nsJSONWriter
+{
+public:
+  nsJSONWriter();
+  nsJSONWriter(nsIOutputStream *aStream);
+  virtual ~nsJSONWriter();
+  nsresult SetCharset(const char *aCharset);
+  nsString mBuffer;
+  nsCOMPtr<nsIOutputStream> mStream;
+  nsresult WriteString(const PRUnichar* aBuffer, PRUint32);
+  nsresult Write(const PRUnichar *aBuffer, PRUint32 aLength);
+
+protected:
+  nsresult WriteToStream(nsIOutputStream *aStream, nsIUnicodeEncoder *encoder,
+                         const PRUnichar *aBuffer, PRUint32 aLength);
+
+  nsCOMPtr<nsIUnicodeEncoder> mEncoder;
+};
+
+class nsJSON : public nsIJSON
+{
+public:
+  nsJSON();
+  virtual ~nsJSON();
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIJSON
+
+protected:
+  JSBool   ToJSON(JSContext *cx, jsval *vp);
+  nsresult EncodeObject(JSContext *cx, jsval *vp, nsJSONWriter *writer,
+                        JSObject *whitelist, PRUint32 depth);
+  nsresult EncodeInternal(nsJSONWriter *writer);
+  nsresult DecodeInternal(nsIInputStream *aStream,
+                          PRInt32 aContentLength,
+                          PRBool aNeedsConverter);
+};
+
+NS_IMETHODIMP
+NS_NewJSON(nsISupports* aOuter, REFNSIID aIID, void** aResult);
+
+enum JSONParserState {
+    JSON_PARSE_STATE_INIT,
+    JSON_PARSE_STATE_VALUE,
+    JSON_PARSE_STATE_OBJECT,
+    JSON_PARSE_STATE_OBJECT_PAIR,
+    JSON_PARSE_STATE_OBJECT_IN_PAIR,
+    JSON_PARSE_STATE_ARRAY,
+    JSON_PARSE_STATE_STRING,
+    JSON_PARSE_STATE_STRING_ESCAPE,
+    JSON_PARSE_STATE_STRING_HEX,
+    JSON_PARSE_STATE_NUMBER,
+    JSON_PARSE_STATE_KEYWORD,
+    JSON_PARSE_STATE_FINISHED
+};
+
+class nsJSONObjectStack : public nsTArray<JSObject *>,
+                          public JSTempValueRooter
+{
+};
+
+class nsJSONListener : public nsIStreamListener
+{
+public:
+  nsJSONListener(JSContext *cx, jsval *rootVal, PRBool needsConverter);
+  virtual ~nsJSONListener();
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIREQUESTOBSERVER
+  NS_DECL_NSISTREAMLISTENER
+
+protected:
+  PRUint32 mLineNum;
+  PRUint32 mColumn;
+
+  /* Used while handling \uNNNN in strings */
+  PRUnichar mHexChar;
+  PRUint8 mNumHex;
+
+  JSContext *mCx;
+  jsval *mRootVal;
+  PRBool mNeedsConverter;
+  nsCOMPtr<nsIUnicodeDecoder> mDecoder;
+  JSONParserState *mStatep;
+  JSONParserState mStateStack[JSON_MAX_DEPTH];
+  nsString mStringBuffer;
+  nsCString mSniffBuffer;
+
+  nsresult PushState(JSONParserState state);
+  nsresult PopState();
+  nsresult ProcessBytes(const char* aBuffer, PRUint32 aByteLength);
+  nsresult ConsumeConverted(const char* aBuffer, PRUint32 aByteLength);
+  nsresult Consume(const PRUnichar *data, PRUint32 len);
+
+  // These handle parsed tokens. Could be split to separate interface.
+  nsJSONObjectStack mObjectStack;
+
+  nsresult PushValue(JSObject *aParent, jsval aValue);
+  nsresult PushObject(JSObject *aObj);
+  nsresult OpenObject();
+  nsresult CloseObject();
+  nsresult OpenArray();
+  nsresult CloseArray();
+  nsresult HandleString();
+  nsresult HandleNumber();
+  nsresult HandleKeyword();
+  nsString mObjectKey;
+};
+
+#endif
new file mode 100644
--- /dev/null
+++ b/dom/src/json/test/Makefile.in
@@ -0,0 +1,49 @@
+#
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2007
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Robert Sayre <sayrer@gmail.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# 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 *****
+
+DEPTH          = ../../../..
+topsrcdir      = @top_srcdir@
+srcdir         = @srcdir@
+VPATH          = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE         = json_test
+
+XPCSHELL_TESTS = unit
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/src/json/test/json2.js
@@ -0,0 +1,263 @@
+/*
+    json2.js
+    2007-11-06
+
+    Public Domain
+
+    See http://www.JSON.org/js.html
+
+    This file creates a global JSON object containing two methods:
+
+        JSON.stringify(value, whitelist)
+            value       any JavaScript value, usually an object or array.
+
+            whitelist   an optional that determines how object values are
+                        stringified.
+
+            This method produces a JSON text from a JavaScript value.
+            There are three possible ways to stringify an object, depending
+            on the optional whitelist parameter.
+
+            If an object has a toJSON method, then the toJSON() method will be
+            called. The value returned from the toJSON method will be
+            stringified.
+
+            Otherwise, if the optional whitelist parameter is an array, then
+            the elements of the array will be used to select members of the
+            object for stringification.
+
+            Otherwise, if there is no whitelist parameter, then all of the
+            members of the object will be stringified.
+
+            Values that do not have JSON representaions, such as undefined or
+            functions, will not be serialized. Such values in objects will be
+            dropped, in arrays will be replaced with null. JSON.stringify()
+            returns undefined. Dates will be stringified as quoted ISO dates.
+
+            Example:
+
+            var text = JSON.stringify(['e', {pluribus: 'unum'}]);
+            // text is '["e",{"pluribus":"unum"}]'
+
+        JSON.parse(text, filter)
+            This method parses a JSON text to produce an object or
+            array. It can throw a SyntaxError exception.
+
+            The optional filter parameter is a function that can filter and
+            transform the results. It receives each of the keys and values, and
+            its return value is used instead of the original value. If it
+            returns what it received, then structure is not modified. If it
+            returns undefined then the member is deleted.
+
+            Example:
+
+            // Parse the text. If a key contains the string 'date' then
+            // convert the value to a date.
+
+            myData = JSON.parse(text, function (key, value) {
+                return key.indexOf('date') >= 0 ? new Date(value) : value;
+            });
+
+    This is a reference implementation. You are free to copy, modify, or
+    redistribute.
+
+    Use your own copy. It is extremely unwise to load third party
+    code into your pages.
+*/
+
+/*jslint evil: true */
+/*extern JSON */
+
+if (!this.JSON) {
+
+    JSON = function () {
+
+        function f(n) {    // Format integers to have at least two digits.
+            return n < 10 ? '0' + n : n;
+        }
+
+        Date.prototype.toJSON = function () {
+
+// Eventually, this method will be based on the date.toISOString method.
+
+            return this.getUTCFullYear()   + '-' +
+                 f(this.getUTCMonth() + 1) + '-' +
+                 f(this.getUTCDate())      + 'T' +
+                 f(this.getUTCHours())     + ':' +
+                 f(this.getUTCMinutes())   + ':' +
+                 f(this.getUTCSeconds())   + 'Z';
+        };
+
+
+        var m = {    // table of character substitutions
+            '\b': '\\b',
+            '\t': '\\t',
+            '\n': '\\n',
+            '\f': '\\f',
+            '\r': '\\r',
+            '"' : '\\"',
+            '\\': '\\\\'
+        };
+
+        function stringify(value, whitelist) {
+            var a,          // The array holding the partial texts.
+                i,          // The loop counter.
+                k,          // The member key.
+                l,          // Length.
+                r = /["\\\x00-\x1f\x7f-\x9f]/g,
+                v;          // The member value.
+
+            switch (typeof value) {
+            case 'string':
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe sequences.
+
+                return r.test(value) ?
+                    '"' + value.replace(r, function (a) {
+                        var c = m[a];
+                        if (c) {
+                            return c;
+                        }
+                        c = a.charCodeAt();
+                        return '\\u00' + Math.floor(c / 16).toString(16) +
+                                                   (c % 16).toString(16);
+                    }) + '"' :
+                    '"' + value + '"';
+
+            case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+                return isFinite(value) ? String(value) : 'null';
+
+            case 'boolean':
+            case 'null':
+                return String(value);
+
+            case 'object':
+
+// Due to a specification blunder in ECMAScript,
+// typeof null is 'object', so watch out for that case.
+
+                if (!value) {
+                    return 'null';
+                }
+
+// If the object has a toJSON method, call it, and stringify the result.
+
+                if (typeof value.toJSON === 'function') {
+                    return stringify(value.toJSON());
+                }
+                a = [];
+                if (typeof value.length === 'number' &&
+                        !(value.propertyIsEnumerable('length'))) {
+
+// The object is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+                    l = value.length;
+                    for (i = 0; i < l; i += 1) {
+                        a.push(stringify(value[i], whitelist) || 'null');
+                    }
+
+// Join all of the elements together and wrap them in brackets.
+
+                    return '[' + a.join(',') + ']';
+                }
+                if (whitelist) {
+
+// If a whitelist (array of keys) is provided, use it to select the components
+// of the object.
+
+                    l = whitelist.length;
+                    for (i = 0; i < l; i += 1) {
+                        k = whitelist[i];
+                        if (typeof k === 'string') {
+                            v = stringify(value[k], whitelist);
+                            if (v) {
+                                a.push(stringify(k) + ':' + v);
+                            }
+                        }
+                    }
+                } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+                    for (k in value) {
+                        if (typeof k === 'string') {
+                            v = stringify(value[k], whitelist);
+                            if (v) {
+                                a.push(stringify(k) + ':' + v);
+                            }
+                        }
+                    }
+                }
+
+// Join all of the member texts together and wrap them in braces.
+
+                return '{' + a.join(',') + '}';
+            }
+						return undefined;
+        }
+
+        return {
+            stringify: stringify,
+            parse: function (text, filter) {
+                var j;
+
+                function walk(k, v) {
+                    var i, n;
+                    if (v && typeof v === 'object') {
+                        for (i in v) {
+                            if (Object.prototype.hasOwnProperty.apply(v, [i])) {
+                                n = walk(i, v[i]);
+                                if (n !== undefined) {
+                                    v[i] = n;
+                                }
+                            }
+                        }
+                    }
+                    return filter(k, v);
+                }
+
+
+// Parsing happens in three stages. In the first stage, we run the text against
+// regular expressions that look for non-JSON patterns. We are especially
+// concerned with '()' and 'new' because they can cause invocation, and '='
+// because it can cause mutation. But just to be safe, we want to reject all
+// unexpected forms.
+
+// We split the first stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace all backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+                if (/^[\],:{}\s]*$/.test(text.replace(/\\./g, '@').
+replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(:?[eE][+\-]?\d+)?/g, ']').
+replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the second stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+                    j = eval('(' + text + ')');
+
+// In the optional third stage, we recursively walk the new structure, passing
+// each name/value pair to a filter function for possible transformation.
+
+                    return typeof filter === 'function' ? walk('', j) : j;
+                }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+                throw new SyntaxError('parseJSON');
+            }
+        };
+    }();
+}
new file mode 100644
--- /dev/null
+++ b/dom/src/json/test/pass1.json
@@ -0,0 +1,58 @@
+[
+    "JSON Test Pattern pass1",
+    {"object with 1 member":["array with 1 element"]},
+    {},
+    [],
+    -42,
+    true,
+    false,
+    null,
+    {
+        "integer": 1234567890,
+        "real": -9876.543210,
+        "e": 0.123456789e-12,
+        "E": 1.234567890E+34,
+        "":  23456789012E66,
+        "zero": 0,
+        "one": 1,
+        "space": " ",
+        "quote": "\"",
+        "backslash": "\\",
+        "controls": "\b\f\n\r\t",
+        "slash": "/ & \/",
+        "alpha": "abcdefghijklmnopqrstuvwyz",
+        "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ",
+        "digit": "0123456789",
+        "0123456789": "digit",
+        "special": "`1~!@#$%^&*()_+-={':[,]}|;.</>?",
+        "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A",
+        "true": true,
+        "false": false,
+        "null": null,
+        "array":[  ],
+        "object":{  },
+        "address": "50 St. James Street",
+        "url": "http://www.JSON.org/",
+        "comment": "// /* <!-- --",
+        "# -- --> */": " ",
+        " s p a c e d " :[1,2 , 3
+
+,
+
+4 , 5        ,          6           ,7        ],"compact":[1,2,3,4,5,6,7],
+        "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}",
+        "quotes": "&#34; \u0022 %22 0x22 034 &#x22;",
+        "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?"
+: "A key can be any string"
+    },
+    0.5 ,98.6
+,
+99.44
+,
+
+1066,
+1e1,
+0.1e1,
+1e-1,
+1e00,2e+00,2e-00
+,"rosebud"]
new file mode 100644
--- /dev/null
+++ b/dom/src/json/test/pass3.json
@@ -0,0 +1,7 @@
+{
+    "JSON Test Pattern pass3": {
+        "The outermost value": "must be an object or array.",
+        "In this test": "It is an object."
+    }
+}
+
new file mode 100644
--- /dev/null
+++ b/dom/src/json/test/unit/head_json.js
@@ -0,0 +1,19 @@
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+
+var nativeJSON = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
+var dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
+var workingDir = dirSvc.get("TmpD", Ci.nsIFile);
+
+var outputName = "json-test-output";
+var outputDir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+outputDir.initWithFile(workingDir);
+outputDir.append(outputName);
+
+if (!outputDir.exists()) {
+  outputDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0777);
+} else if (!outputDir.isDirectory()) {
+  do_throw(outputName + " is not a directory?")
+}
+var JSON = null;
+do_import_script("dom/src/json/test/json2.js");
new file mode 100644
--- /dev/null
+++ b/dom/src/json/test/unit/test_decode.js
@@ -0,0 +1,161 @@
+function decode_strings() {
+  // empty object
+  var x = nativeJSON.decode("{}");
+  do_check_eq(typeof x, "object");
+
+  // empty array
+  x = nativeJSON.decode("[]");
+  do_check_eq(typeof x, "object");
+  do_check_eq(x.length, 0);
+  do_check_eq(x.constructor, Array);
+
+  // one element array
+  x = nativeJSON.decode("[[]]");
+  do_check_eq(typeof x, "object");
+  do_check_eq(x.length, 1);
+  do_check_eq(x.constructor, Array);
+  do_check_eq(x[0].constructor, Array);
+
+  // multiple arrays
+  x = nativeJSON.decode("[[],[],[]]");
+  do_check_eq(typeof x, "object");
+  do_check_eq(x.length, 3);
+  do_check_eq(x.constructor, Array);
+  do_check_eq(x[0].constructor, Array);
+  do_check_eq(x[1].constructor, Array);
+  do_check_eq(x[2].constructor, Array);
+
+  // array key/value
+  x = nativeJSON.decode('{"foo":[]}');
+  do_check_eq(typeof x, "object");
+  do_check_eq(typeof x.foo, "object");
+  do_check_eq(x.foo.constructor, Array);
+  x = nativeJSON.decode('{"foo":[], "bar":[]}');
+  do_check_eq(typeof x, "object");
+  do_check_eq(typeof x.foo, "object");
+  do_check_eq(x.foo.constructor, Array);
+  do_check_eq(typeof x.bar, "object");
+  do_check_eq(x.bar.constructor, Array);
+
+  // nesting
+  x = nativeJSON.decode('{"foo":[{}]}');
+  do_check_eq(x.foo.constructor, Array);
+  do_check_eq(x.foo.length, 1);
+  do_check_eq(typeof x.foo[0], "object");
+  x = nativeJSON.decode('{"foo":[{"foo":[{"foo":{}}]}]}');
+  do_check_eq(x.foo[0].foo[0].foo.constructor, Object);
+  x = nativeJSON.decode('{"foo":[{"foo":[{"foo":[]}]}]}');
+  do_check_eq(x.foo[0].foo[0].foo.constructor, Array);
+
+  // strings
+  x = nativeJSON.decode('{"foo":"bar"}');
+  do_check_eq(x.foo, "bar");
+  x = nativeJSON.decode('["foo", "bar", "baz"]');
+  do_check_eq(x[0], "foo");
+  do_check_eq(x[1], "bar");
+  do_check_eq(x[2], "baz");
+
+  // numbers
+  x = nativeJSON.decode('{"foo":5.5, "bar":5}');
+  do_check_eq(x.foo, 5.5);
+  do_check_eq(x.bar, 5);
+  
+  // keywords
+  x = nativeJSON.decode('{"foo": true, "bar":false, "baz":null}');
+  do_check_eq(x.foo, true);
+  do_check_eq(x.bar, false);
+  do_check_eq(x.baz, null);
+  
+  // short escapes
+  x = nativeJSON.decode('{"foo": "\\"", "bar":"\\\\", "baz":"\\b","qux":"\\f", "quux":"\\n", "quuux":"\\r","quuuux":"\\t"}');
+  do_check_eq(x.foo, '"');
+  do_check_eq(x.bar, '\\');
+  do_check_eq(x.baz, '\b');
+  do_check_eq(x.qux, '\f');
+  do_check_eq(x.quux, "\n");
+  do_check_eq(x.quuux, "\r");
+  do_check_eq(x.quuuux, "\t");
+
+  // unicode escape
+  x = nativeJSON.decode('{"foo":"hmm\\u006dmm"}');
+  do_check_eq("hmm\u006dmm", x.foo);
+
+  x = nativeJSON.decode('{"JSON Test Pattern pass3": {"The outermost value": "must be an object or array.","In this test": "It is an object." }}');
+}
+
+function test_files() {
+  function read_file(path) {
+    try {
+      var f = do_get_file(path);
+      var istream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
+      istream.init(f, -1, -1, false);
+      var x = nativeJSON.decodeFromStream(istream, istream.available());
+    } finally {
+      istream.close();
+    }
+    return x;
+  }
+
+  var x = read_file("/dom/src/json/test/pass3.json");
+  do_check_eq(x["JSON Test Pattern pass3"]["The outermost value"], "must be an object or array.");
+  do_check_eq(x["JSON Test Pattern pass3"]["In this test"], "It is an object.");
+
+  x = read_file("/dom/src/json/test/pass1.json");
+  do_check_eq(x[0], "JSON Test Pattern pass1");
+  do_check_eq(x[1]["object with 1 member"][0], "array with 1 element");
+  do_check_eq(x[2].constructor, Object);
+  do_check_eq(x[3].constructor, Array);
+  do_check_eq(x[4], -42);
+  do_check_eq(x[5], true);
+  do_check_eq(x[6], false);
+  do_check_eq(x[7], null);
+  do_check_eq(x[8].constructor, Object);
+  do_check_eq(x[8]["integer"], 1234567890);
+  do_check_eq(x[8]["real"], -9876.543210);
+  do_check_eq(x[8]["e"], 0.123456789e-12);
+  do_check_eq(x[8]["E"], 1.234567890E+34);
+  do_check_eq(x[8][""], 23456789012E66);
+  do_check_eq(x[8]["zero"], 0);
+  do_check_eq(x[8]["one"], 1);
+  do_check_eq(x[8]["space"], " ");
+  do_check_eq(x[8]["quote"], "\"");
+  do_check_eq(x[8]["backslash"], "\\");
+  do_check_eq(x[8]["controls"], "\b\f\n\r\t");
+  do_check_eq(x[8]["slash"], "/ & /");
+  do_check_eq(x[8]["alpha"], "abcdefghijklmnopqrstuvwyz");
+  do_check_eq(x[8]["ALPHA"], "ABCDEFGHIJKLMNOPQRSTUVWYZ");
+  do_check_eq(x[8]["digit"], "0123456789");
+  do_check_eq(x[8]["0123456789"], "digit");
+  do_check_eq(x[8]["special"], "`1~!@#$%^&*()_+-={':[,]}|;.</>?");
+  do_check_eq(x[8]["hex"], "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A");
+  do_check_eq(x[8]["true"], true);
+  do_check_eq(x[8]["false"], false);
+  do_check_eq(x[8]["null"], null);
+  do_check_eq(x[8]["array"].length, 0);
+  do_check_eq(x[8]["object"].constructor, Object);
+  do_check_eq(x[8]["address"], "50 St. James Street");
+  do_check_eq(x[8]["url"], "http://www.JSON.org/");
+  do_check_eq(x[8]["comment"], "// /* <!-- --");
+  do_check_eq(x[8]["# -- --> */"], " ");
+  do_check_eq(x[8][" s p a c e d "].length, 7);
+  do_check_eq(x[8]["compact"].length, 7);
+  do_check_eq(x[8]["jsontext"], "{\"object with 1 member\":[\"array with 1 element\"]}");
+  do_check_eq(x[8]["quotes"], "&#34; \u0022 %22 0x22 034 &#x22;");
+  do_check_eq(x[8]["\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?"], "A key can be any string");
+  do_check_eq(x[9], 0.5);
+  do_check_eq(x[10], 98.6);
+  do_check_eq(x[11], 99.44);
+  do_check_eq(x[12], 1066);
+  do_check_eq(x[13], 1e1);
+  do_check_eq(x[14], 0.1e1);
+  do_check_eq(x[15], 1e-1);
+  do_check_eq(x[16], 1e00);
+  do_check_eq(x[17], 2e+00);
+  do_check_eq(x[18], 2e-00);
+  do_check_eq(x[19], "rosebud");
+}
+
+function run_test() {
+  decode_strings();
+  test_files();
+}
new file mode 100644
--- /dev/null
+++ b/dom/src/json/test/unit/test_encode.js
@@ -0,0 +1,237 @@
+// returns a list of [string, object] pairs to test encoding
+function getTestPairs() {
+  var testPairs = [
+    ["{}", {}],
+    ["[]", []],
+    ['{"foo":"bar"}', {"foo":"bar"}],
+    ['{"null":null}', {"null":null}],
+    ['{"five":5}', {"five":5}],
+    ['{"five":5,"six":6}', {"five":5, "six":6}],
+    ['{"x":{"y":"z"}}', {"x":{"y":"z"}}],
+    ['{"w":{"x":{"y":"z"}}}', {"w":{"x":{"y":"z"}}}],
+    ['[1,2,3]', [1,2,3]],
+    ['{"w":{"x":{"y":[1,2,3]}}}', {"w":{"x":{"y":[1,2,3]}}}],
+    ['{"false":false}', {"false":false}],
+    ['{"true":true}', {"true":true}],
+    ['{"child has two members":{"this":"one","2":"and this one"}}',
+     {"child has two members": {"this":"one", 2:"and this one"}}],
+    ['{"x":{"a":"b","c":{"y":"z"},"f":"g"}}',
+     {"x":{"a":"b","c":{"y":"z"},"f":"g"}}],
+    ['{"x":[1,{"y":"z"},3]}', {"x":[1,{"y":"z"},3]}],
+    //['{"0":"h","1":"m","2":"m"}', new String("hmm")],
+    ['[1,null,3]',[1,,3]],
+    [null, function test(){}],
+    [null, dump],
+    ['{"mm\\\"mm":"hmm"}',{"mm\"mm":"hmm"}],
+    ['{"mm\\\"mm\\\"mm":"hmm"}',{"mm\"mm\"mm":"hmm"}],
+    ['{"\\\"":"hmm"}',{'"':"hmm"}],
+    ['{"\\\\":"hmm"}',{'\\':"hmm"}],
+    ['{"mmm\\\\mmm":"hmm"}',{'mmm\\mmm':"hmm"}],
+    ['{"mmm\\\\mmm\\\\mmm":"hmm"}',{'mmm\\mmm\\mmm':"hmm"}],
+    ['{"mm\\u000bmm":"hmm"}',{"mm\u000bmm":"hmm"}],
+    ['{"mm\\u0000mm":"hmm"}',{"mm\u0000mm":"hmm"}]
+  ];
+
+  var x = {"free":"variable"}
+  testPairs.push(['{"free":"variable"}', x]);
+  testPairs.push(['{"y":{"free":"variable"}}', {"y":x}]);
+
+  // array prop
+  var x = {
+    a: [1,2,3]
+  }
+  testPairs.push(['{"a":[1,2,3]}', x])
+
+  var y = {
+    foo: function(hmm) { return hmm; }
+  }
+  testPairs.push(['{"y":{}}',{"y":y}]);
+
+  // test toJSON
+  var hmm = {
+    toJSON: function() { return {"foo":"bar"}}
+  }
+  testPairs.push(['{"hmm":{"foo":"bar"}}', {"hmm":hmm}]);
+  testPairs.push(['{"foo":"bar"}', hmm]); // on the root
+
+  // toJSON on prototype
+  var Y = function() {
+    this.d = "e";
+  }
+  Y.prototype = {
+    not:"there?",
+    toJSON: function() { return {"foo":"bar"}}
+  };
+  var y = new Y();
+  testPairs.push(['{"foo":"bar"}', y.toJSON()]);
+  testPairs.push(['{"foo":"bar"}', y]);
+
+  // return undefined from toJSON
+  var hmm = {
+    toJSON: function() { return; }
+  }
+  testPairs.push(['{}', {"hmm":hmm}]);
+
+  // array with named prop
+  var x= new Array();
+  x[0] = 1;
+  x.foo = "bar";
+  //testPairs.push(['[1]', x]);
+
+  // prototype
+  var X = function() { this.a = "b" }
+  X.prototype = {c:"d"}
+  var y = new X();
+  testPairs.push(['{"a":"b","c":"d"}', y]);
+
+  // useless roots will be dropped
+  testPairs.push([null, null]);
+  testPairs.push([null, ""]);
+  testPairs.push([null, undefined]);
+  testPairs.push([null, 5]);
+
+  // custom iterator: JS 1.7+
+  var x = {
+   "a": "foo",
+   b: "not included",
+   c: "bar",
+   "4": "qux",
+   __iterator__: function() { return (function() { yield "a"; yield "c"; yield 4; })() }
+  }
+  do_check_eq('{"a":"foo","c":"bar","4":"qux"}', nativeJSON.encode(x));
+
+  return testPairs;
+}
+
+function testStringEncode() {
+  // test empty arg
+  do_check_eq(null, nativeJSON.encode());
+
+  var pairs = getTestPairs();
+  for each(pair in pairs) {
+    var nativeResult = nativeJSON.encode(pair[1]);
+    var crockfordResult = JSON.stringify(pair[1]);
+    do_check_eq(pair[0], nativeResult);
+    
+    // Don't follow json2.js handling of non-objects
+    if (pair[1] && (typeof pair[1] == "object")) {
+      do_check_eq(crockfordResult, nativeResult);
+    }
+  }
+}
+
+function testOutputStreams() {
+  function writeToFile(obj, charset, writeBOM) {
+    var jsonFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+    jsonFile.initWithFile(outputDir);
+    jsonFile.append("test.json");
+    jsonFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
+    var stream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
+    try {
+      stream.init(jsonFile, 0x04 | 0x08 | 0x20, 0600, 0); // write, create, truncate
+      nativeJSON.encodeToStream(stream, charset, writeBOM, obj);
+    } finally {
+      stream.close();
+    }
+    return jsonFile;
+  }
+
+  var pairs = getTestPairs();
+  for each(pair in pairs) {
+    if (pair[1] && (typeof pair[1] == "object")) {
+      var utf8File = writeToFile(pair[1], "UTF-8", false);
+      var utf16LEFile = writeToFile(pair[1], "UTF-16LE", false);
+      var utf16BEFile = writeToFile(pair[1], "UTF-16BE", false);
+      var utf32LEFile = writeToFile(pair[1], "UTF-32LE", false);
+      var utf32BEFile = writeToFile(pair[1], "UTF-32BE", false);
+
+      // all ascii with no BOMs, so this will work
+      do_check_eq(utf16LEFile.fileSize / 2, utf8File.fileSize);
+      do_check_eq(utf32LEFile.fileSize / 4, utf8File.fileSize);
+      do_check_eq(utf16LEFile.fileSize, utf16BEFile.fileSize);
+      do_check_eq(utf32LEFile.fileSize, utf32BEFile.fileSize);
+    }
+  }
+
+  // check BOMs
+  var f = writeToFile({},"UTF-8", true);
+  do_check_eq(f.fileSize, 5);
+  var f = writeToFile({},"UTF-16LE", true);
+  do_check_eq(f.fileSize, 6);
+  var f = writeToFile({},"UTF-16BE", true);
+  do_check_eq(f.fileSize, 6);
+  var f = writeToFile({},"UTF-32LE", true);
+  do_check_eq(f.fileSize, 12);
+  var f = writeToFile({},"UTF-32BE", true);
+  do_check_eq(f.fileSize, 12);
+  
+  outputDir.remove(true);
+}
+
+function throwingToJSON() {
+  var a = {
+    "b": 1,
+    "c": 2,
+    toJSON: function() { throw("uh oh"); }
+  }
+  try {
+    var y = nativeJSON.encode(a);
+  } catch (ex) {}
+}
+
+function throwingIterator() {
+  var a = {
+    "b": 1,
+    "c": 2,
+    __iterator__: function() { yield "b"; throw("uh oh"); }
+  }
+  try {
+    var y = nativeJSON.encode(a);
+  } catch (ex) {}
+}
+
+function deletingIter(x) {
+  return function() {
+    yield "dd";
+    print("after first yield");
+    delete x["a"]["c"];
+    gc();
+    print("about to yield second");
+    yield "ddddd";
+  }
+}
+
+function deleteDuringEncode() {
+  var x = {};
+  x.a = {
+    b: 1,
+    bb: 2,
+    bbb: 3,
+    c: {
+      cc: 2,
+      ccc: 3,
+      d: {
+        dd: 2,
+        ddd: 3,
+        __iterator__: deletingIter(x),
+        dddd: 4,
+        ddddd: 5
+      },
+      cccc: 4,
+      ccccc: 5
+    },
+    bbbb: 4,
+    bbbbb: 5,
+    bbbbbb: 6
+  };
+  var z = nativeJSON.encode(x);
+  print(z);
+}
+
+function run_test() {
+  testStringEncode();
+  testOutputStreams();
+  throwingToJSON();
+  throwingIterator();
+  deleteDuringEncode();
+}
--- a/js/src/Makefile.in
+++ b/js/src/Makefile.in
@@ -131,16 +131,17 @@ EXPORTS		= \
 		jsbool.h \
 		jsclist.h \
 		jscntxt.h \
 		jscompat.h \
 		jsconfig.h \
 		jsdate.h \
 		jsdbgapi.h \
 		jsdhash.h \
+		jsdtoa.h \
 		jsemit.h \
 		jsfun.h \
 		jsgc.h \
 		jshash.h \
 		jsinterp.h \
 		jsiter.h \
 		jslock.h \
 		jslong.h \
--- a/js/src/jsiter.c
+++ b/js/src/jsiter.c
@@ -323,17 +323,17 @@ js_GetNativeIteratorFlags(JSContext *cx,
         return 0;
     return JSVAL_TO_INT(OBJ_GET_SLOT(cx, iterobj, JSSLOT_ITER_FLAGS));
 }
 
 /*
  * Call ToObject(v).__iterator__(keyonly) if ToObject(v).__iterator__ exists.
  * Otherwise construct the defualt iterator.
  */
-JSBool
+JS_FRIEND_API(JSBool)
 js_ValueToIterator(JSContext *cx, uintN flags, jsval *vp)
 {
     JSObject *obj;
     JSTempValueRooter tvr;
     JSAtom *atom;
     JSClass *clasp;
     JSExtendedClass *xclasp;
     JSBool ok;
@@ -434,17 +434,17 @@ js_ValueToIterator(JSContext *cx, uintN 
     if (obj)
         JS_POP_TEMP_ROOT(cx, &tvr);
     return ok;
   bad:
     ok = JS_FALSE;
     goto out;
 }
 
-JSBool
+JS_FRIEND_API(JSBool)
 js_CloseIterator(JSContext *cx, jsval v)
 {
     JSObject *obj;
     JSClass *clasp;
 
     JS_ASSERT(!JSVAL_IS_PRIMITIVE(v));
     obj = JSVAL_TO_OBJECT(v);
     clasp = OBJ_GET_CLASS(cx, obj);
@@ -588,17 +588,17 @@ CallEnumeratorNext(JSContext *cx, JSObje
     return JS_TRUE;
 
   stop:
     JS_ASSERT(STOBJ_GET_SLOT(iterobj, JSSLOT_ITER_STATE) == JSVAL_NULL);
     *rval = JSVAL_HOLE;
     return JS_TRUE;
 }
 
-JSBool
+JS_FRIEND_API(JSBool)
 js_CallIteratorNext(JSContext *cx, JSObject *iterobj, jsval *rval)
 {
     uintN flags;
 
     /* Fast path for native iterators */
     if (OBJ_GET_CLASS(cx, iterobj) == &js_IteratorClass) {
         flags = JSVAL_TO_INT(OBJ_GET_SLOT(cx, iterobj, JSSLOT_ITER_FLAGS));
         if (flags & JSITER_ENUMERATE)
--- a/js/src/jsiter.h
+++ b/js/src/jsiter.h
@@ -53,27 +53,27 @@ JS_BEGIN_EXTERN_C
 #define JSITER_KEYVALUE   0x4   /* destructuring for-in wants [key, value] */
 
 /*
  * Convert the value stored in *vp to its iteration object. The flags should
  * contain JSITER_ENUMERATE if js_ValueToIterator is called when enumerating
  * for-in semantics are required, and when the caller can guarantee that the
  * iterator will never be exposed to scripts.
  */
-extern JSBool
+extern JS_FRIEND_API(JSBool)
 js_ValueToIterator(JSContext *cx, uintN flags, jsval *vp);
 
-extern JSBool
+extern JS_FRIEND_API(JSBool)
 js_CloseIterator(JSContext *cx, jsval v);
 
 /*
  * Given iterobj, call iterobj.next().  If the iterator stopped, set *rval to
  * JSVAL_HOLE. Otherwise set it to the result of the next call.
  */
-extern JSBool
+extern JS_FRIEND_API(JSBool)
 js_CallIteratorNext(JSContext *cx, JSObject *iterobj, jsval *rval);
 
 /*
  * Close iterobj, whose class must be js_IteratorClass.
  */
 extern void
 js_CloseNativeIterator(JSContext *cx, JSObject *iterobj);
 
--- a/layout/build/Makefile.in
+++ b/layout/build/Makefile.in
@@ -136,16 +136,17 @@ SHARED_LIBRARY_LIBS = \
 	$(DEPTH)/content/xslt/src/xml/$(LIB_PREFIX)txxml_s.$(LIB_SUFFIX) \
 	$(DEPTH)/content/xslt/src/xpath/$(LIB_PREFIX)txxpath_s.$(LIB_SUFFIX) \
 	$(DEPTH)/content/xslt/src/xslt/$(LIB_PREFIX)txxslt_s.$(LIB_SUFFIX) \
 	$(DEPTH)/content/xbl/src/$(LIB_PREFIX)gkconxbl_s.$(LIB_SUFFIX) \
 	$(DEPTH)/content/xul/document/src/$(LIB_PREFIX)gkconxuldoc_s.$(LIB_SUFFIX) \
 	$(DEPTH)/view/src/$(LIB_PREFIX)gkview_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/src/base/$(LIB_PREFIX)jsdombase_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/src/events/$(LIB_PREFIX)jsdomevents_s.$(LIB_SUFFIX) \
+	$(DEPTH)/dom/src/json/$(LIB_PREFIX)json_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/src/jsurl/$(LIB_PREFIX)jsurl_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/src/storage/$(LIB_PREFIX)jsdomstorage_s.$(LIB_SUFFIX) \
 	$(DEPTH)/dom/src/offline/$(LIB_PREFIX)jsdomoffline_s.$(LIB_SUFFIX) \
 	$(DEPTH)/editor/libeditor/text/$(LIB_PREFIX)texteditor_s.$(LIB_SUFFIX) \
 	$(DEPTH)/editor/libeditor/base/$(LIB_PREFIX)editorbase_s.$(LIB_SUFFIX) \
 	$(NULL)
 
 ifdef NS_PRINTING
@@ -272,16 +273,17 @@ LOCAL_INCLUDES	+= -I$(srcdir)/../base \
 		   -I$(topsrcdir)/content/xslt/src/xslt \
 		   -I$(topsrcdir)/content/xul/content/src \
 		   -I$(topsrcdir)/content/xul/document/src \
 		   -I$(topsrcdir)/content/xul/templates/src \
 		   -I$(topsrcdir)/content/events/src \
 		   -I$(topsrcdir)/content/xbl/src \
 		   -I$(topsrcdir)/view/src \
 		   -I$(topsrcdir)/dom/src/base \
+		   -I$(topsrcdir)/dom/src/json \
 		   -I$(topsrcdir)/dom/src/jsurl \
 		   -I$(topsrcdir)/dom/src/storage \
 		   -I$(topsrcdir)/dom/src/offline \
 		   -I. \
 		   -I$(topsrcdir)/editor/libeditor/base \
 		   -I$(topsrcdir)/editor/libeditor/text \
 		   -I$(topsrcdir)/editor/libeditor/html \
 		   -I$(topsrcdir)/editor/txtsvc/src \
--- a/layout/build/nsLayoutCID.h
+++ b/layout/build/nsLayoutCID.h
@@ -217,9 +217,12 @@
 // {b88a4712-eb52-4c10-9b85-bf5894b510f0}
 #define NS_DOMSTORAGEMANAGER_CID               \
 { 0xb88a4712, 0xeb52, 0x4c10, { 0x9b, 0x85, 0xbf, 0x58, 0x94, 0xb5, 0x10, 0xf0 } }
 
 // {14632191-AC21-4BDF-83E7-2363AD17E838}
 #define NS_XULPOPUPMANAGER_CID \
 { 0x14632191, 0xac21, 0x4bdf, { 0x83, 0xe7, 0x23, 0x63, 0xad, 0x17, 0xe8, 0x38 } }
 
+// {93ad72a6-02cd-4716-9626-d47d5ec275ec}
+#define NS_DOMJSON_CID \
+{ 0x93ad72a6, 0x02cd, 0x4716, { 0x96, 0x26, 0xd4, 0x7d, 0x5e, 0xc2, 0x75, 0xec } }
 #endif /* nsLayoutCID_h__ */
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -120,16 +120,17 @@
 #include "nsDOMException.h"
 #include "nsGlobalWindowCommands.h"
 #include "nsIControllerCommandTable.h"
 #include "nsJSProtocolHandler.h"
 #include "nsScriptNameSpaceManager.h"
 #include "nsIControllerContext.h"
 #include "nsDOMScriptObjectFactory.h"
 #include "nsDOMStorage.h"
+#include "nsJSON.h"
 
 // Editor stuff
 #include "nsEditorCID.h"
 #include "nsEditor.h"
 #include "nsPlaintextEditor.h"
 #include "nsEditorController.h" //CID
 #include "nsIController.h"
 #include "nsIControllerContext.h"
@@ -1316,16 +1317,21 @@ static const nsModuleComponentInfo gComp
     "@mozilla.org/dom/storage;1",
     NS_NewDOMStorage },
 
   { "DOM Storage Manager",
     NS_DOMSTORAGEMANAGER_CID,
     "@mozilla.org/dom/storagemanager;1",
     nsDOMStorageManagerConstructor },
 
+  { "DOM JSON",
+    NS_DOMJSON_CID,
+    "@mozilla.org/dom/json;1",
+    NS_NewJSON },
+
   { "Text Editor",
     NS_TEXTEDITOR_CID,
     "@mozilla.org/editor/texteditor;1",
     nsPlaintextEditorConstructor },
 
 #ifndef MOZILLA_PLAINTEXT_EDITOR_ONLY
 #ifdef ENABLE_EDITOR_API_LOG
     { "HTML Editor",
--- a/layout/style/nsCSSLoader.cpp
+++ b/layout/style/nsCSSLoader.cpp
@@ -458,46 +458,20 @@ static nsresult GetCharsetFromData(const
   // that way even if we don't have a valid @charset rule we can use the BOM to
   // get a reasonable charset.  If we do have an @charset rule, the string from
   // that will override this fallback setting of aCharset.
   if (*aStyleSheetData == 0x40 && *(aStyleSheetData+1) == 0x63 /* '@c' */ ) {
     // 1-byte ASCII-based encoding (ISO-8859-*, UTF-8, etc), no BOM
     step = 1;
     pos = 0;
   }
-  else if (aStyleSheetData[0] == 0xEF &&
-           aStyleSheetData[1] == 0xBB &&
-           aStyleSheetData[2] == 0xBF) {
-    // UTF-8 BOM
-    step = 1;
-    pos = 3;
-    aCharset = "UTF-8";
-  }
   // Check for a 4-byte encoding BOM before checking for a 2-byte one,
   // since the latter can be a proper subset of the former.
   else if (aStyleSheetData[0] == 0x00 &&
            aStyleSheetData[1] == 0x00 &&
-           aStyleSheetData[2] == 0xFE &&
-           aStyleSheetData[3] == 0xFF) {
-    // big-endian 4-byte encoding BOM
-    step = 4;
-    pos = 7;
-    aCharset = "UTF-32BE";
-  }
-  else if (aStyleSheetData[0] == 0xFF &&
-           aStyleSheetData[1] == 0xFE &&
-           aStyleSheetData[2] == 0x00 &&
-           aStyleSheetData[3] == 0x00) {
-    // little-endian 4-byte encoding BOM
-    step = 4;
-    pos = 4;
-    aCharset = "UTF-32LE";
-  }
-  else if (aStyleSheetData[0] == 0x00 &&
-           aStyleSheetData[1] == 0x00 &&
            aStyleSheetData[2] == 0xFF &&
            aStyleSheetData[3] == 0xFE) {
     // 4-byte encoding BOM in 2143 order
     NS_WARNING("Our unicode decoders aren't likely  to deal with this one");
     step = 4;
     pos = 6;
     aCharset = "UTF-32";
   }
@@ -506,27 +480,38 @@ static nsresult GetCharsetFromData(const
            aStyleSheetData[2] == 0x00 &&
            aStyleSheetData[3] == 0x00) {
     // 4-byte encoding BOM in 3412 order
     NS_WARNING("Our unicode decoders aren't likely  to deal with this one");
     step = 4;
     pos = 5;
     aCharset = "UTF-32";
   }
-  else if (aStyleSheetData[0] == 0xFE && aStyleSheetData[1] == 0xFF) {
-    // big-endian 2-byte encoding BOM
-    step = 2;
-    pos = 3;
-    aCharset = "UTF-16BE";
-  }
-  else if (aStyleSheetData[0] == 0xFF && aStyleSheetData[1] == 0xFE) {
-    // little-endian 2-byte encoding BOM
-    step = 2;
-    pos = 2;
-    aCharset = "UTF-16LE";
+  else if (nsContentUtils::CheckForBOM(aStyleSheetData,
+                                       aDataLength, aCharset)) {
+    if (aCharset.Equals("UTF-8")) {
+      step = 1;
+      pos = 3;
+    }
+    else if (aCharset.Equals("UTF-32BE")) {
+      step = 4;
+      pos = 7;
+    }
+    else if (aCharset.Equals("UTF-32LE")) {
+      step = 4;
+      pos = 4;
+    }
+    else if (aCharset.Equals("UTF-16BE")) {
+      step = 2;
+      pos = 3;
+    }
+    else if (aCharset.Equals("UTF-16LE")) {
+      step = 2;
+      pos = 2;
+    }
   }
   else if (aStyleSheetData[0] == 0x00 &&
            aStyleSheetData[1] == 0x00 &&
            aStyleSheetData[2] == 0x00 &&
            aStyleSheetData[3] == 0x40) {
     // big-endian 4-byte encoding, no BOM
     step = 4;
     pos = 3;
--- a/tools/test-harness/xpcshell-simple/test_all.sh
+++ b/tools/test-harness/xpcshell-simple/test_all.sh
@@ -106,17 +106,17 @@ done
 
 #################
 # RUN EACH TEST #
 #################
 
 for t in $testdir/test_*.js
 do
     echo -n "$t: "
-    NATIVE_TOPSRCDIR="$native_topsrcdir" TOPSRCDIR="$topsrcdir" $xpcshell -s $headfiles -f $t $tailfiles 2> $t.log 1>&2
+    NATIVE_TOPSRCDIR="$native_topsrcdir" TOPSRCDIR="$topsrcdir" $xpcshell -v 180 -s $headfiles -f $t $tailfiles 2> $t.log 1>&2
     rv="$?"
     if [ ! "$rv" = "0"  -o \
          `grep -c '\*\*\* PASS' $t.log` = 0 ]
     then
         echo "FAIL"
         echo "$t.log:"
         echo ">>>>>>>"
         cat $t.log