Bug 408838. Native JSON. r/sr=shaver
authorRobert Sayre <sayrer@gmail.com>
Mon, 06 Oct 2008 16:54:12 -0400
changeset 20092 4d331883f18d0c60463c5a9c2fe49e8f6fd1b02b
parent 20090 ffbab1273403cf939e0bd28b982f97c1564835cf
child 20093 6ecedbcf4d050047b78a04cf778f0c190858320c
push idunknown
push userunknown
push dateunknown
bugs408838
milestone1.9.1b1pre
Bug 408838. Native JSON. r/sr=shaver
dom/public/idl/json/nsIJSON.idl
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/test_json.html
dom/src/json/test/unit/head_json.js
dom/src/json/test/unit/test_encode.js
js/src/Makefile.in
js/src/Makefile.ref
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsatom.cpp
js/src/jsatom.h
js/src/json.cpp
js/src/json.h
js/src/jsproto.tbl
js/src/jspubtd.h
--- a/dom/public/idl/json/nsIJSON.idl
+++ b/dom/public/idl/json/nsIJSON.idl
@@ -43,24 +43,20 @@ 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 */);
+  AString encode(/* in JSObject value */);
 
   void encodeToStream(in nsIOutputStream stream,
                       in string charset,
                       in boolean writeBOM
-                      /* in JSObject value,
-                      /* [optional] in JSObject optFilter */);
+                      /* in JSObject value */);
 
-  void /* JSObject */ decode(in AString str
-                             /* , [optional] in JSObject whitelist */);
+  void /* JSObject */ decode(in AString str);
 
   void /* JSObject */ decodeFromStream(in nsIInputStream stream,
-                                       in long contentLength
-                                       /*[optional] in JSObject optFilter */);
+                                       in long contentLength);
 };
--- a/dom/src/json/nsJSON.cpp
+++ b/dom/src/json/nsJSON.cpp
@@ -57,16 +57,18 @@
 #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";
 
+#define JSON_STREAM_BUFSIZE 1024
+
 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)
 
@@ -176,16 +178,27 @@ nsJSON::EncodeToStream(nsIOutputStream *
   rv = EncodeInternal(&writer);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = bufferedStream->Flush();
 
   return rv;
 }
 
+static JSBool
+WriteCallback(const jschar *buf, uint32 len, void *data)
+{
+  nsJSONWriter *writer = static_cast<nsJSONWriter*>(data);
+  nsresult rv =  writer->Write((const PRUnichar*)buf, (PRUint32)len);
+  if (NS_FAILED(rv))
+    return JS_FALSE;
+
+  return JS_TRUE;
+}
+
 nsresult
 nsJSON::EncodeInternal(nsJSONWriter *writer)
 {
   nsresult rv;
   nsIXPConnect *xpc = nsContentUtils::XPConnect();
   if (!xpc)
     return NS_ERROR_FAILURE;
 
@@ -216,238 +229,32 @@ nsJSON::EncodeInternal(nsJSONWriter *wri
 
   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);
+  JSBool ok = JS_TryJSON(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);
+  ok = JS_Stringify(cx, vp, NULL, &WriteCallback, writer);
+  if (!ok)
+    return NS_ERROR_FAILURE;
+    
+  return NS_OK;
 }
 
-// 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;
-  JSObject *iterObj = nsnull;
-  jsint i = 0;
-  jsuint length = 0;
-
-  if (isArray) {
-    ok = JS_GetArrayLength(cx, obj, &length);
-    if (!ok)
-      return NS_ERROR_FAILURE;
-  } else {
-    ok = js_ValueToIterator(cx, JSITER_ENUMERATE, vp);
-    if (!ok)
-      return NS_ERROR_FAILURE;
-
-    iterObj = JSVAL_TO_OBJECT(*vp);
-  }
-
-  jsval outputValue = JSVAL_VOID;
-  JSAutoTempValueRooter tvr(cx, 1, &outputValue);
-
-  jsval key;
-  PRBool memberWritten = PR_FALSE;
-  do {
-    outputValue = JSVAL_VOID;
-
-    if (isArray) {
-      if ((jsuint)i >= length)
-        break;
-
-      ok = JS_GetElement(cx, obj, i++, &outputValue);
-    } else {
-      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));
-
-  if (iterObj) {
-    // 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;
-  const 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),
                                mBuffer(nsnull),
                                mBufferCount(0),
                                mDidWrite(PR_FALSE),
                                mEncoder(nsnull)
 {
 }
@@ -478,84 +285,36 @@ nsJSONWriter::SetCharset(const char* aCh
     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)
 {
   if (mStream) {
     return WriteToStream(mStream, mEncoder, aBuffer, aLength);
   }
 
   if (!mDidWrite) {
-    mBuffer = new PRUnichar[JSON_PARSER_BUFSIZE];
+    mBuffer = new PRUnichar[JSON_STREAM_BUFSIZE];
     if (!mBuffer)
       return NS_ERROR_OUT_OF_MEMORY;
     mDidWrite = PR_TRUE;
   }
 
-  if (JSON_PARSER_BUFSIZE <= aLength + mBufferCount) {
+  if (JSON_STREAM_BUFSIZE <= aLength + mBufferCount) {
     mOutputString.Append(mBuffer, mBufferCount);
     mBufferCount = 0;
   }
 
-  if (JSON_PARSER_BUFSIZE <= aLength) {
+  if (JSON_STREAM_BUFSIZE <= aLength) {
     // we know mBufferCount is 0 because we know we hit the if above
     mOutputString.Append(aBuffer, aLength);
   } else {
     memcpy(&mBuffer[mBufferCount], aBuffer, aLength * sizeof(PRUnichar));
     mBufferCount += aLength;
   }
 
   return NS_OK;
@@ -718,79 +477,67 @@ NS_NewJSON(nsISupports* aOuter, REFNSIID
     return NS_ERROR_OUT_OF_MEMORY;
 
   NS_ADDREF(json);
   *aResult = json;
 
   return NS_OK;
 }
 
-
-static 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)
-  : mHexChar(0),
-    mNumHex(0),
+  : mNeedsConverter(needsConverter), 
+    mJSONParser(nsnull),
     mCx(cx),
-    mRootVal(rootVal),
-    mNeedsConverter(needsConverter),
-    mStatep(mStateStack)
+    mRootVal(rootVal)
 {
-  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);
+  Cleanup();
 }
 
 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;
+  mJSONParser = JS_BeginJSONParse(mCx, mRootVal);
+  if (!mJSONParser)
+    return NS_ERROR_FAILURE;
+
   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)
+  JSBool ok = JS_FinishJSONParse(mCx, mJSONParser);
+  mJSONParser = nsnull;
+
+  if (!ok)
     return NS_ERROR_FAILURE;
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsJSONListener::OnDataAvailable(nsIRequest *aRequest, nsISupports *aContext,
                                 nsIInputStream *aStream,
@@ -804,17 +551,17 @@ nsJSONListener::OnDataAvailable(nsIReque
     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];
+  char buffer[JSON_STREAM_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);
@@ -896,462 +643,28 @@ nsJSONListener::ConsumeConverted(const c
   rv = mDecoder->Convert(aBuffer, &srcLen, ustr, &unicharLength);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = Consume(ustr.get(), unicharLength);
 
   return rv;
 }
 
-nsresult
-nsJSONListener::PopState()
+void nsJSONListener::Cleanup()
 {
-  mStatep--;
-  if (mStatep < mStateStack) {
-    mStatep = mStateStack;
+  if (mJSONParser)
+    JS_FinishJSONParse(mCx, mJSONParser);
+  mJSONParser = nsnull;
+}
+
+nsresult
+nsJSONListener::Consume(const PRUnichar* aBuffer, PRUint32 aByteLength)
+{
+  if (!mJSONParser)
     return NS_ERROR_FAILURE;
-  } else if (*mStatep == JSON_PARSE_STATE_INIT) {
-    *mStatep = JSON_PARSE_STATE_FINISHED;
+
+  if (!JS_ConsumeJSONText(mCx, mJSONParser, (jschar*) aBuffer, aByteLength)) {
+    Cleanup();
+    return NS_ERROR_FAILURE;
   }
 
   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;
-  PRUint32 i;
-
-  // we'll try to avoid string munging during parsing
-  PRUnichar buf[JSON_PARSER_BUFSIZE + 1];
-  PRUint32 bufIndex = 0;
-
-#define PUSHCHAR(_c)                         \
-if (bufIndex == JSON_PARSER_BUFSIZE) {       \
-  mStringBuffer.Append(buf, bufIndex);       \
-  bufIndex = 0;                              \
-}                                            \
-buf[bufIndex] = _c;                          \
-bufIndex++;
-
-  if (*mStatep == JSON_PARSE_STATE_INIT) {
-    PushState(JSON_PARSE_STATE_OBJECT_VALUE);
-  }
-
-  for (i = 0; i < len; i++) {
-    PRUnichar c = data[i];
-
-    switch (*mStatep) {
-      case JSON_PARSE_STATE_VALUE :
-        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);
-          break;
-        } 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;
-          break;
-        } else if (IsNumChar(c)) {
-          *mStatep = JSON_PARSE_STATE_NUMBER;
-          PUSHCHAR(c);
-          break;
-        } else if (NS_IsAsciiAlpha(c)) {
-          *mStatep = JSON_PARSE_STATE_KEYWORD;
-          PUSHCHAR(c);
-          break;
-        } 
-        // fall through in case the value is an object or array
-      case JSON_PARSE_STATE_OBJECT_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 (!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);
-          buf[bufIndex] = nsnull;
-          if (*mStatep == JSON_PARSE_STATE_OBJECT_IN_PAIR) {
-            rv = HandleData(JSON_DATA_KEYSTRING, buf, bufIndex);
-          } else {
-            rv = HandleData(JSON_DATA_STRING, buf, bufIndex);
-          }
-          bufIndex = 0;
-          NS_ENSURE_SUCCESS(rv, rv);
-        } else if (c == '\\') {
-          *mStatep = JSON_PARSE_STATE_STRING_ESCAPE;
-        } else {
-          PUSHCHAR(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
-            }
-        }
-
-        PUSHCHAR(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) {
-          PUSHCHAR(mHexChar);
-          mHexChar = 0;
-          mNumHex = 0;
-          *mStatep = JSON_PARSE_STATE_STRING;
-        }
-        break;
-      case JSON_PARSE_STATE_KEYWORD:
-        if (NS_IsAsciiAlpha(c)) {
-          PUSHCHAR(c);
-        } else {
-          // this character isn't part of the keyword, process it again
-          i--;
-          rv = PopState();
-          NS_ENSURE_SUCCESS(rv, rv);
-          buf[bufIndex] = nsnull;
-          rv = HandleData(JSON_DATA_KEYWORD, buf, bufIndex);
-          bufIndex = 0;
-          NS_ENSURE_SUCCESS(rv, rv);
-        }
-        break;
-      case JSON_PARSE_STATE_NUMBER:
-        if (IsNumChar(c)) {
-          PUSHCHAR(c);
-        } else {
-          // this character isn't part of the number, process it again
-          i--;
-          rv = PopState();
-          NS_ENSURE_SUCCESS(rv, rv);
-          buf[bufIndex] = nsnull;
-          rv = HandleData(JSON_DATA_NUMBER, buf, bufIndex);
-          bufIndex = 0;
-          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");
-      }
-    }
-
-#undef PUSH_CHAR
-
-    // Preserve partially consumed data for the next call to Consume
-    // This can happen when a primitive spans a stream buffer
-    if (bufIndex != 0) {
-      mStringBuffer.Append(buf, bufIndex);
-    }
-
-    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()
-{
-  JSObject *obj = JS_NewObject(mCx, NULL, NULL, NULL);
-  if (!obj)
-    return NS_ERROR_OUT_OF_MEMORY;
-
-  return PushObject(obj);
-}
-
-nsresult
-nsJSONListener::OpenArray()
-{
-  // 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::HandleData(JSONDataType aType, const PRUnichar *aBuf,
-                           PRUint32 aLength)
-{
-  nsresult rv = NS_OK;
-  PRUint32 len;
-  const PRUnichar *buf;
-  PRBool needsTruncate = PR_FALSE;
-
-  if (mStringBuffer.IsEmpty()) {
-    buf = aBuf;
-    len = aLength;
-  } else {
-    needsTruncate = PR_TRUE;
-    mStringBuffer.Append(aBuf, aLength);
-    buf = mStringBuffer.get();
-    len = mStringBuffer.Length();
-  }
-
-  switch (aType) {
-    case JSON_DATA_STRING:
-      rv = HandleString(buf, len);
-      break;
-
-    case JSON_DATA_KEYSTRING:
-      mObjectKey = nsDependentString(buf, len);
-      rv = NS_OK;
-      break;
-
-    case JSON_DATA_NUMBER:
-      rv = HandleNumber(buf, len);
-      break;
-
-    case JSON_DATA_KEYWORD:
-      rv = HandleKeyword(buf, len);
-      break;
-
-    default:
-      NS_NOTREACHED("Should have a JSON data type");
-  }
-
-  if (needsTruncate)
-    mStringBuffer.Truncate();
-
-  return rv;
-}
-
-nsresult
-nsJSONListener::HandleString(const PRUnichar *aBuf, PRUint32 aLength)
-{
-  JSObject *obj = mObjectStack.ElementAt(mObjectStack.Length() - 1);
-  JSString *str = JS_NewUCStringCopyN(mCx, 
-                                      reinterpret_cast<const jschar*> (aBuf),
-                                      aLength);
-  if (!str)
-    return NS_ERROR_OUT_OF_MEMORY;
-
-  return PushValue(obj, STRING_TO_JSVAL(str));
-}
-
-nsresult
-nsJSONListener::HandleNumber(const PRUnichar *aBuf, PRUint32 aLength)
-{
-  nsresult rv;
-  JSObject *obj = mObjectStack.ElementAt(mObjectStack.Length() - 1);
-
-  char *estr;
-  int err;
-  double val =
-    JS_strtod(NS_ConvertUTF16toUTF8(nsDependentString(aBuf, aLength)).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
-    }
-  }
-
-  return rv;
-}
-
-nsresult
-nsJSONListener::HandleKeyword(const PRUnichar *aBuf, PRUint32 aLength)
-{
-  nsAutoString buf;
-  buf.Append(aBuf, aLength);
-
-  JSObject *obj = mObjectStack.ElementAt(mObjectStack.Length() - 1);
-  jsval keyword;
-  if (buf.Equals(NS_LITERAL_STRING("null"))) {
-    keyword = JSVAL_NULL;
-  } else if (buf.Equals(NS_LITERAL_STRING("true"))) {
-    keyword = JSVAL_TRUE;
-  } else if (buf.Equals(NS_LITERAL_STRING("false"))) {
-    keyword = JSVAL_FALSE;
-  } else {
-    return NS_ERROR_FAILURE;
-  }
-
-  return PushValue(obj, keyword);
-}
--- a/dom/src/json/nsJSON.h
+++ b/dom/src/json/nsJSON.h
@@ -35,40 +35,37 @@
  * 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 "jsapi.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"
 
 class nsIURI;
 
-#define JSON_MAX_DEPTH  2048
-#define JSON_PARSER_BUFSIZE 1024
-
 class NS_STACK_CLASS nsJSONWriter
 {
 public:
   nsJSONWriter();
   nsJSONWriter(nsIOutputStream *aStream);
   virtual ~nsJSONWriter();
   nsresult SetCharset(const char *aCharset);
   nsCOMPtr<nsIOutputStream> mStream;
-  nsresult WriteString(const PRUnichar* aBuffer, PRUint32);
   nsresult Write(const PRUnichar *aBuffer, PRUint32 aLength);
   nsString mOutputString;
   PRBool DidWrite();
   void FlushBuffer();
 
 protected:
   PRUnichar *mBuffer;
   PRUint32 mBufferCount;
@@ -84,109 +81,42 @@ 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);
   nsCOMPtr<nsIURI> mURI;
 };
 
 NS_IMETHODIMP
 NS_NewJSON(nsISupports* aOuter, REFNSIID aIID, void** aResult);
 
-enum JSONParserState {
-    JSON_PARSE_STATE_INIT,
-    JSON_PARSE_STATE_OBJECT_VALUE,
-    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
-};
-
-enum JSONDataType {
-  JSON_DATA_STRING,
-  JSON_DATA_KEYSTRING,
-  JSON_DATA_NUMBER,
-  JSON_DATA_KEYWORD
-};
-
-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:
-
-  /* Used while handling \uNNNN in strings */
-  PRUnichar mHexChar;
-  PRUint8 mNumHex;
-
+  PRBool mNeedsConverter;
+  JSONParser *mJSONParser;
   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);
-
-  // helper to determine whether a character could be part of a number
-  PRBool IsNumChar(PRUnichar c) 
-  {
-    if ((c <= '9' && c >= '0') ||
-        c == '.' || c == '-' || c == '+' || c == 'e' || c == 'E')
-      return PR_TRUE;
-
-    return PR_FALSE;
-  }
-
-  // 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 HandleData(JSONDataType aType, const PRUnichar *aBuf,
-                      PRUint32 aLength);
-  nsresult HandleString(const PRUnichar *aBuf, PRUint32 aLength);
-  nsresult HandleNumber(const PRUnichar *aBuf, PRUint32 aLength);
-  nsresult HandleKeyword(const PRUnichar *aBuf, PRUint32 aLength);
-  nsString mObjectKey;
+  void Cleanup();
 };
 
 #endif
--- a/dom/src/json/test/Makefile.in
+++ b/dom/src/json/test/Makefile.in
@@ -34,16 +34,24 @@
 # the terms of any one of the MPL, the GPL or the LGPL.
 #
 # ***** END LICENSE BLOCK *****
 
 DEPTH          = ../../../..
 topsrcdir      = @top_srcdir@
 srcdir         = @srcdir@
 VPATH          = @srcdir@
+relativesrcdir  = dom/src/json/test
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE         = json_test
 
 XPCSHELL_TESTS = unit
 
 include $(topsrcdir)/config/rules.mk
+
+ _TEST_FILES =	test_json.html \
+		$(NULL)
+
+libs:: $(_TEST_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
+
--- a/dom/src/json/test/json2.js
+++ b/dom/src/json/test/json2.js
@@ -63,19 +63,19 @@
 
     Use your own copy. It is extremely unwise to load third party
     code into your pages.
 */
 
 /*jslint evil: true */
 /*extern JSON */
 
-if (!this.JSON) {
+if (!this.crockfordJSON) {
 
-    JSON = function () {
+    crockfordJSON = 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.
new file mode 100644
--- /dev/null
+++ b/dom/src/json/test/test_json.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=408838
+-->
+<head>
+  <title>Test for Bug 408838</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=408838">Mozilla Bug 408838</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 408838 **/
+
+is(typeof JSON, "object", "JSON should be present");
+is(typeof JSON.parse, "function", "JSON.parse should be present");
+is(typeof JSON.stringify, "function", "JSON.stringify should be present");
+
+var str = '{"foo":[1,{"bar":"baz"}],"qux":1234.1234,"quux":[true,false,null,"hmm",9,[]]}';
+var x = JSON.parse(str);
+
+is(x.foo[0], 1, "JSON integer");
+is(x.foo[1].bar, "baz", "JSON string in object");
+is(x.qux, 1234.1234, "JSON float");
+
+var y = JSON.stringify(x);
+is(y, str, "JSON round trip");
+
+</script>
+</pre>
+</body>
+</html>
+
--- a/dom/src/json/test/unit/head_json.js
+++ b/dom/src/json/test/unit/head_json.js
@@ -10,10 +10,10 @@ var outputDir = Cc["@mozilla.org/file/lo
 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;
+var crockfordJSON = null;
 do_import_script("dom/src/json/test/json2.js");
--- a/dom/src/json/test/unit/test_encode.js
+++ b/dom/src/json/test/unit/test_encode.js
@@ -105,17 +105,17 @@ function getTestPairs() {
 
 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]);
+    var crockfordResult = crockfordJSON.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);
     }
   }
 }
--- a/js/src/Makefile.in
+++ b/js/src/Makefile.in
@@ -135,16 +135,17 @@ CPPSRCS		= \
 		jsinvoke.cpp \
 		jsiter.cpp \
 		jslock.cpp \
 		jslog2.cpp \
 		jslong.cpp \
 		jsmath.cpp \
 		jsnum.cpp \
 		jsobj.cpp \
+		json.cpp \
 		jsopcode.cpp \
 		jsparse.cpp \
 		jsprf.cpp \
 		jsregexp.cpp \
 		jsscan.cpp \
 		jsscope.cpp \
 		jsscript.cpp \
 		jsstr.cpp \
@@ -182,16 +183,17 @@ EXPORTS		= \
 		jshash.h \
 		jsinterp.h \
 		jsiter.h \
 		jslock.h \
 		jslong.h \
 		jsmath.h \
 		jsnum.h \
 		jsobj.h \
+		json.h \
 		jsopcode.tbl \
 		jsopcode.h \
 		jsotypes.h \
 		jsparse.h \
 		jsprf.h \
 		jsproto.tbl \
 		jsprvtd.h \
 		jspubtd.h \
--- a/js/src/Makefile.ref
+++ b/js/src/Makefile.ref
@@ -183,17 +183,18 @@ JS_HFILES =		\
 	jsgc.h		\
 	jsinterp.h	\
 	jsiter.h	\
 	jslibmath.h	\
 	jslock.h	\
 	jsmath.h	\
 	jsnum.h		\
 	jsobj.h		\
-	jsopcode.h      \
+	json.h		\
+	jsopcode.h	\
 	jsparse.h	\
 	jsarena.h	\
 	jsclist.h	\
 	jsdhash.h	\
 	jsdtoa.h	\
 	jshash.h	\
 	jslong.h	\
 	jstypes.h	\
@@ -282,16 +283,17 @@ JS_CPPFILES =		\
 	jsinvoke.cpp    \
 	jsiter.cpp	\
 	jslock.cpp	\
 	jslog2.cpp	\
 	jslong.cpp	\
 	jsmath.cpp	\
 	jsnum.cpp	\
 	jsobj.cpp	\
+	json.cpp	\
 	jsopcode.cpp	\
 	jsparse.cpp	\
 	jsprf.cpp	\
 	jsregexp.cpp	\
 	jsscan.cpp	\
 	jsscope.cpp	\
 	jsscript.cpp	\
 	jsstr.cpp	\
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -64,16 +64,17 @@
 #include "jsexn.h"
 #include "jsfun.h"
 #include "jsgc.h"
 #include "jsinterp.h"
 #include "jsiter.h"
 #include "jslock.h"
 #include "jsmath.h"
 #include "jsnum.h"
+#include "json.h"
 #include "jsobj.h"
 #include "jsopcode.h"
 #include "jsparse.h"
 #include "jsregexp.h"
 #include "jsscan.h"
 #include "jsscope.h"
 #include "jsscript.h"
 #include "jsstr.h"
@@ -1345,16 +1346,17 @@ JS_InitStandardClasses(JSContext *cx, JS
     /* Initialize the rest of the standard objects and functions. */
     return js_InitArrayClass(cx, obj) &&
            js_InitBlockClass(cx, obj) &&
            js_InitBooleanClass(cx, obj) &&
            js_InitCallClass(cx, obj) &&
            js_InitExceptionClasses(cx, obj) &&
            js_InitMathClass(cx, obj) &&
            js_InitNumberClass(cx, obj) &&
+           js_InitJSONClass(cx, obj) &&
            js_InitRegExpClass(cx, obj) &&
            js_InitStringClass(cx, obj) &&
            js_InitEval(cx, obj) &&
 #if JS_HAS_SCRIPT_OBJECT
            js_InitScriptClass(cx, obj) &&
 #endif
 #if JS_HAS_XML_SUPPORT
            js_InitXMLClasses(cx, obj) &&
@@ -1428,16 +1430,17 @@ static JSStdName standard_class_atoms[] 
     {js_InitQNameClass,                 EAGER_ATOM_AND_EXT_CLASP(QName)},
 #endif
 #if JS_HAS_FILE_OBJECT
     {js_InitFileClass,                  EAGER_ATOM_AND_CLASP(File)},
 #endif
 #if JS_HAS_GENERATORS
     {js_InitIteratorClasses,            EAGER_ATOM_AND_CLASP(StopIteration)},
 #endif
+    {js_InitJSONClass,                  EAGER_ATOM_AND_CLASP(JSON)},
     {NULL,                              0, NULL, NULL}
 };
 
 /*
  * Table of top-level function and constant names and their init functions.
  * If you add a "standard" global function or property, remember to update
  * this table.
  */
@@ -5518,16 +5521,52 @@ JS_DecodeBytes(JSContext *cx, const char
 }
 
 JS_PUBLIC_API(char *)
 JS_EncodeString(JSContext *cx, JSString *str)
 {
     return js_DeflateString(cx, JSSTRING_CHARS(str), JSSTRING_LENGTH(str));
 }
 
+JS_PUBLIC_API(JSBool)
+JS_Stringify(JSContext *cx, jsval *vp, JSObject *replacer, 
+             JSONWriteCallback callback, void *data)
+{
+    CHECK_REQUEST(cx);
+    return js_Stringify(cx, vp, replacer, callback, data, 0);
+}
+
+JS_PUBLIC_API(JSBool)
+JS_TryJSON(JSContext *cx, jsval *vp)
+{
+    CHECK_REQUEST(cx);
+    return js_TryJSON(cx, vp);
+}
+
+JS_PUBLIC_API(JSONParser *)
+JS_BeginJSONParse(JSContext *cx, jsval *vp)
+{
+    CHECK_REQUEST(cx);
+    return js_BeginJSONParse(cx, vp);
+}
+
+JS_PUBLIC_API(JSBool)
+JS_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len)
+{
+    CHECK_REQUEST(cx);
+    return js_ConsumeJSONText(cx, jp, data, len);
+}
+
+JS_PUBLIC_API(JSBool)
+JS_FinishJSONParse(JSContext *cx, JSONParser *jp)
+{
+    CHECK_REQUEST(cx);
+    return js_FinishJSONParse(cx, jp);
+}
+
 /*
  * The following determines whether C Strings are to be treated as UTF-8
  * or ISO-8859-1.  For correct operation, it must be set prior to the
  * first call to JS_NewRuntime.
  */
 #ifndef JS_C_STRINGS_ARE_UTF8
 JSBool js_CStringsAreUTF8 = JS_FALSE;
 #endif
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -2391,16 +2391,47 @@ JS_DecodeBytes(JSContext *cx, const char
 /*
  * A variation on JS_EncodeCharacters where a null terminated string is
  * returned that you are expected to call JS_free on when done.
  */
 JS_PUBLIC_API(char *)
 JS_EncodeString(JSContext *cx, JSString *str);
 
 /************************************************************************/
+/*
+ * JSON functions
+ */
+typedef JSBool (* JSONWriteCallback)(const jschar *buf, uint32 len, void *data);
+
+/*
+ * JSON.stringify as specificed by ES3.1 (draft)
+ */
+JS_PUBLIC_API(JSBool)
+JS_Stringify(JSContext *cx, jsval *vp, JSObject *replacer, 
+             JSONWriteCallback callback, void *data);
+
+/*
+ * Retrieve a toJSON function. If found, set vp to its result.
+ */
+JS_PUBLIC_API(JSBool)
+JS_TryJSON(JSContext *cx, jsval *vp);
+
+/*
+ * JSON.parse as specificed by ES3.1 (draft)
+ */
+JS_PUBLIC_API(JSONParser *)
+JS_BeginJSONParse(JSContext *cx, jsval *vp);
+
+JS_PUBLIC_API(JSBool)
+JS_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len);
+
+JS_PUBLIC_API(JSBool)
+JS_FinishJSONParse(JSContext *cx, JSONParser *jp);
+
+/************************************************************************/
 
 /*
  * Locale specific string conversion and error message callbacks.
  */
 struct JSLocaleCallbacks {
     JSLocaleToUpperCase     localeToUpperCase;
     JSLocaleToLowerCase     localeToLowerCase;
     JSLocaleCompare         localeCompare;
--- a/js/src/jsatom.cpp
+++ b/js/src/jsatom.cpp
@@ -121,16 +121,17 @@ const char *const js_common_atom_names[]
     js_proto_str,               /* protoAtom                    */
     js_set_str,                 /* setAtom                      */
     js_setter_str,              /* setterAtom                   */
     js_stack_str,               /* stackAtom                    */
     js_toLocaleString_str,      /* toLocaleStringAtom           */
     js_toSource_str,            /* toSourceAtom                 */
     js_toString_str,            /* toStringAtom                 */
     js_valueOf_str,             /* valueOfAtom                  */
+    js_toJSON_str,              /* toJSONAtom                   */
     "(void 0)",                 /* void0Atom                    */
 
 #if JS_HAS_XML_SUPPORT
     js_etago_str,               /* etagoAtom                    */
     js_namespace_str,           /* namespaceAtom                */
     js_ptagc_str,               /* ptagcAtom                    */
     js_qualifier_str,           /* qualifierAtom                */
     js_space_str,               /* spaceAtom                    */
@@ -180,16 +181,17 @@ const char js_proto_str[]           = "_
 const char js_setter_str[]          = "setter";
 const char js_set_str[]             = "set";
 const char js_stack_str[]           = "stack";
 const char js_toSource_str[]        = "toSource";
 const char js_toString_str[]        = "toString";
 const char js_toLocaleString_str[]  = "toLocaleString";
 const char js_undefined_str[]       = "undefined";
 const char js_valueOf_str[]         = "valueOf";
+const char js_toJSON_str[]          = "toJSON";
 
 #if JS_HAS_XML_SUPPORT
 const char js_etago_str[]           = "</";
 const char js_namespace_str[]       = "namespace";
 const char js_ptagc_str[]           = "/>";
 const char js_qualifier_str[]       = "::";
 const char js_space_str[]           = " ";
 const char js_stago_str[]           = "<";
--- a/js/src/jsatom.h
+++ b/js/src/jsatom.h
@@ -197,16 +197,17 @@ struct JSAtomState {
     JSAtom              *protoAtom;
     JSAtom              *setAtom;
     JSAtom              *setterAtom;
     JSAtom              *stackAtom;
     JSAtom              *toLocaleStringAtom;
     JSAtom              *toSourceAtom;
     JSAtom              *toStringAtom;
     JSAtom              *valueOfAtom;
+    JSAtom              *toJSONAtom;
     JSAtom              *void0Atom;
 
 #if JS_HAS_XML_SUPPORT
     JSAtom              *etagoAtom;
     JSAtom              *namespaceAtom;
     JSAtom              *ptagcAtom;
     JSAtom              *qualifierAtom;
     JSAtom              *spaceAtom;
@@ -332,16 +333,17 @@ extern const char   js_stago_str[];
 extern const char   js_star_str[];
 extern const char   js_starQualifier_str[];
 extern const char   js_tagc_str[];
 extern const char   js_toSource_str[];
 extern const char   js_toString_str[];
 extern const char   js_toLocaleString_str[];
 extern const char   js_undefined_str[];
 extern const char   js_valueOf_str[];
+extern const char   js_toJSON_str[];
 extern const char   js_xml_str[];
 
 #ifdef NARCISSUS
 extern const char   js_call_str[];
 extern const char   js_construct_str[];
 extern const char   js_hasInstance_str[];
 extern const char   js_ExecutionContext_str[];
 extern const char   js_current_str[];
new file mode 100644
--- /dev/null
+++ b/js/src/json.cpp
@@ -0,0 +1,916 @@
+/* ***** 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 SpiderMonkey JSON.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998-1999
+ * 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 ***** */
+
+
+#include "jsapi.h"
+#include "jsarena.h"
+#include "jsarray.h"
+#include "jsatom.h"
+#include "jsbool.h"
+#include "jscntxt.h"
+#include "jsdtoa.h"
+#include "jsinterp.h"
+#include "jsiter.h"
+#include "jsnum.h"
+#include "jsobj.h"
+#include "jsprf.h"
+#include "jsscan.h"
+#include "jsstr.h"
+#include "jstypes.h"
+#include "jsutil.h"
+
+#include "json.h"
+
+JSClass js_JSONClass = {
+    js_JSON_str,
+    JSCLASS_HAS_CACHED_PROTO(JSProto_JSON),
+    JS_PropertyStub,  JS_PropertyStub,  JS_PropertyStub,  JS_PropertyStub,
+    JS_EnumerateStub, JS_ResolveStub,   JS_ConvertStub,   JS_FinalizeStub,
+    JSCLASS_NO_OPTIONAL_MEMBERS
+};
+
+JSBool
+js_json_parse(JSContext *cx, uintN argc, jsval *vp)
+{
+    JSString *s = NULL;
+    jsval *argv = vp + 2;
+
+    // Must throw an Error if there isn't a first arg
+    if (!JS_ConvertArguments(cx, argc, argv, "S", &s))
+        return JS_FALSE;
+
+
+    JSONParser *jp = js_BeginJSONParse(cx, vp);
+    JSBool ok = jp != NULL;
+
+    if (ok) {
+        ok = js_ConsumeJSONText(cx, jp, JS_GetStringChars(s), JS_GetStringLength(s));
+        ok &= js_FinishJSONParse(cx, jp);
+    }
+
+    if (!ok)
+        JS_ReportError(cx, "Error parsing JSON.");
+
+    return ok;
+}
+
+struct StringifyClosure
+{
+    StringifyClosure(JSContext *aCx, jsval *str) : cx(aCx), s(str)
+    {
+    }
+
+    JSContext *cx;
+    jsval *s;
+};
+
+static
+JSBool WriteCallback(const jschar *buf, uint32 len, void *data)
+{
+    StringifyClosure *sc = static_cast<StringifyClosure*>(data);
+    JSString *s1 = JSVAL_TO_STRING(*sc->s);
+    JSString *s2 = JS_NewUCStringCopyN(sc->cx, buf, len);
+    if (!s2)
+        return JS_FALSE;
+
+    s1 = js_ConcatStrings(sc->cx, s1, s2);
+    if (!s1)
+        return JS_FALSE;
+
+    *sc->s = STRING_TO_JSVAL(s1);
+    return JS_TRUE;
+}
+
+JSBool
+js_json_stringify(JSContext *cx, uintN argc, jsval *vp)
+{
+    JSObject *obj;
+    jsval *argv = vp + 2;
+    
+    // Must throw an Error if there isn't a first arg
+    if (!JS_ConvertArguments(cx, argc, argv, "o", &obj))
+        return JS_FALSE;
+
+    // Only use objects and arrays as the root for now
+    jsval v = OBJECT_TO_JSVAL(obj);
+    JSBool ok = js_TryJSON(cx, &v);
+    JSType type;
+    if (!(ok && !JSVAL_IS_PRIMITIVE(v) &&
+          (type = JS_TypeOfValue(cx, v)) != JSTYPE_FUNCTION &&
+          type != JSTYPE_XML)) {
+        JS_ReportError(cx, "Invalid argument.");
+        return JS_FALSE;
+    }
+    
+    JSString *s = JS_NewStringCopyN(cx, "", 0);
+    if (!s)
+        ok = JS_FALSE;
+
+    if (ok) {
+        jsval sv = STRING_TO_JSVAL(s);
+        StringifyClosure sc(cx, &sv);
+	JSAutoTempValueRooter tvr(cx, 1, sc.s); 
+        ok = js_Stringify(cx, &v, NULL, &WriteCallback, &sc, 0);
+        *vp = *sc.s;
+    }
+
+    return ok;
+}
+
+JSBool
+js_TryJSON(JSContext *cx, jsval *vp)
+{
+    // Checks whether the return value implements toJSON()
+    JSBool ok = JS_TRUE;
+    
+    if (!JSVAL_IS_PRIMITIVE(*vp)) {
+        JSObject *obj = JSVAL_TO_OBJECT(*vp);
+        ok = js_TryMethod(cx, obj, cx->runtime->atomState.toJSONAtom, 0, NULL, vp);
+    }
+    
+    return ok;
+}
+
+
+static const jschar quote = jschar('"');
+static const jschar backslash = jschar('\\');
+static const jschar unicodeEscape[] = {'\\', 'u', '0', '0'};
+
+static JSBool
+write_string(JSContext *cx, JSONWriteCallback callback, void *data, const jschar *buf, uint32 len)
+{
+    if (!callback(&quote, 1, data))
+        return JS_FALSE;
+
+    uint32 mark = 0;
+    uint32 i;
+    for (i = 0; i < len; ++i) {
+        if (buf[i] == quote || buf[i] == backslash) {
+            if (!callback(&buf[mark], i - mark, data) || !callback(&backslash, 1, data) ||
+                !callback(&buf[i], 1, data)) {
+                return JS_FALSE;
+            }
+            mark = i + 1;
+        } else if (buf[i] <= 31 || buf[i] == 127) {
+            if (!callback(&buf[mark], i - mark, data) || !callback(unicodeEscape, 4, data))
+                return JS_FALSE;
+            char ubuf[10];
+            unsigned int len = JS_snprintf(ubuf, sizeof(ubuf), "%.2x", buf[i]);
+            JS_ASSERT(len == 2);
+            // TODO: don't allocate a JSString just to inflate (js_InflateStringToBuffer on static?)
+            JSString *us = JS_NewStringCopyN(cx, ubuf, len);
+            if (!callback(JS_GetStringChars(us), len, data))
+                return JS_FALSE;
+            mark = i + 1;
+        }
+    }
+
+    if (mark < len && !callback(&buf[mark], len - mark, data))
+        return JS_FALSE;
+
+    if (!callback(&quote, 1, data))
+        return JS_FALSE;
+
+    return JS_TRUE;
+}
+
+JSBool
+js_Stringify(JSContext *cx, jsval *vp, JSObject *replacer,
+             JSONWriteCallback callback, void *data, uint32 depth)
+{
+    if (depth > JSON_MAX_DEPTH)
+        return JS_FALSE; /* encoding error */
+
+    JSBool ok = JS_TRUE;
+    JSObject *obj = JSVAL_TO_OBJECT(*vp);
+    JSBool isArray = JS_IsArrayObject(cx, obj);
+    jschar output = jschar(isArray ? '[' : '{');
+    if (!callback(&output, 1, data))
+        return JS_FALSE;
+    
+    JSObject *iterObj = NULL;
+    jsint i = 0;
+    jsuint length = 0;
+
+    if (isArray) {
+        if (!JS_GetArrayLength(cx, obj, &length))
+            return JS_FALSE;
+    } else {
+        if (!js_ValueToIterator(cx, JSITER_ENUMERATE, vp))
+            return JS_FALSE;
+        iterObj = JSVAL_TO_OBJECT(*vp);
+    }
+
+    jsval outputValue = JSVAL_VOID;
+    JSAutoTempValueRooter tvr(cx, 1, &outputValue);
+
+    jsval key;
+    JSBool memberWritten = JS_FALSE;
+    do {
+        outputValue = JSVAL_VOID;
+
+        if (isArray) {
+            if ((jsuint)i >= length)
+                break;
+            ok = JS_GetElement(cx, obj, i++, &outputValue);
+        } else {
+            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 = js_TryJSON(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 = jschar(',');
+            ok = callback(&output, 1, data);
+        if (!ok)
+                break;
+        }
+        memberWritten = JS_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) {
+            s = JS_ValueToString(cx, key);
+            if (!s) {
+                ok = JS_FALSE;
+                break;
+            }
+
+            ok = write_string(cx, callback, data, JS_GetStringChars(s), JS_GetStringLength(s));
+            if (!ok)
+                break;
+
+            output = jschar(':');
+            ok = callback(&output, 1, data);
+            if (!ok)
+                break;
+        }
+
+        if (!JSVAL_IS_PRIMITIVE(outputValue)) {
+            // recurse
+          ok = js_Stringify(cx, &outputValue, replacer, callback, data, depth + 1);
+        } else {
+            JSString *outputString;
+            s = JS_ValueToString(cx, outputValue);
+            if (!s) {
+                ok = JS_FALSE;
+                break;
+            }
+
+            if (type == JSTYPE_STRING) {
+                ok = write_string(cx, callback, data, JS_GetStringChars(s), JS_GetStringLength(s));
+                if (!ok)
+                    break;
+                
+                continue;
+            }
+
+            if (type == JSTYPE_NUMBER) {
+                if (JSVAL_IS_DOUBLE(outputValue)) {
+                    jsdouble d = *JSVAL_TO_DOUBLE(outputValue);
+                    if (!JSDOUBLE_IS_FINITE(d))
+                        outputString = JS_NewStringCopyN(cx, "null", 4);
+                    else
+                        outputString = s;
+                } else {
+                    outputString = s;
+                }
+            } else if (type == JSTYPE_BOOLEAN) {
+                outputString = s;
+            } else if (JSVAL_IS_NULL(outputValue)) {
+                outputString = JS_NewStringCopyN(cx, "null", 4);
+            } else {
+                ok = JS_FALSE; // encoding error
+                break;
+            }
+
+            ok = callback(JS_GetStringChars(outputString), JS_GetStringLength(outputString), data);
+        }
+    } while (ok);
+
+    if (iterObj) {
+        // Always close the iterator, but make sure not to stomp on OK
+        ok &= js_CloseIterator(cx, *vp);
+        // encoding error or propagate? FIXME: Bug 408838.
+    }
+
+    if (!ok) {
+        JS_ReportError(cx, "Error during JSON encoding.");
+        return JS_FALSE;
+    }
+
+    output = jschar(isArray ? ']' : '}');
+    ok = callback(&output, 1, data);
+
+    return ok;
+}
+
+// helper to determine whether a character could be part of a number
+static JSBool IsNumChar(jschar c) 
+{
+    return ((c <= '9' && c >= '0') || c == '.' || c == '-' || c == '+' || c == 'e' || c == 'E');
+}
+
+JSONParser *
+js_BeginJSONParse(JSContext *cx, jsval *rootVal)
+{
+    if (!cx)
+        return NULL;
+
+    JSObject *arr = JS_NewArrayObject(cx, 0, NULL);
+    if (!arr)
+        return NULL;
+
+    JSONParser *jp = (JSONParser*) JS_malloc(cx, sizeof(JSONParser));
+    if (!jp)
+        return NULL;        
+    jp->buffer = NULL;
+
+    jp->objectStack = arr;
+    if (!JS_AddRoot(cx, jp->objectStack))
+        goto bad;
+
+    jp->hexChar = 0;
+    jp->numHex = 0;
+    jp->statep = jp->stateStack;
+    *jp->statep = JSON_PARSE_STATE_INIT;
+    jp->rootVal = rootVal;
+    jp->objectKey = NULL;
+    jp->buffer = (JSStringBuffer*) JS_malloc(cx, sizeof(JSStringBuffer));
+    if (!jp->buffer)
+        goto bad;
+    js_InitStringBuffer(jp->buffer);
+
+    return jp;
+bad:
+    JS_free(cx, jp->buffer);
+    JS_free(cx, jp);
+    return NULL;
+}
+
+JSBool
+js_FinishJSONParse(JSContext *cx, JSONParser *jp)
+{
+    if (!jp)
+        return JS_TRUE;
+
+    if (jp->buffer)
+        js_FinishStringBuffer(jp->buffer);
+    
+    JS_free(cx, jp->buffer);
+    if (!JS_RemoveRoot(cx, jp->objectStack))
+        return JS_FALSE;
+    JSBool ok = *jp->statep == JSON_PARSE_STATE_FINISHED;
+    JS_free(cx, jp);
+
+    return ok;
+}
+
+
+static JSBool
+PushState(JSONParser *jp, JSONParserState state)
+{
+    if (*jp->statep == JSON_PARSE_STATE_FINISHED)
+        return JS_FALSE; // extra input
+
+    jp->statep++;
+    if ((uint32)(jp->statep - jp->stateStack) >= JS_ARRAY_LENGTH(jp->stateStack))
+        return JS_FALSE; // too deep
+
+    *jp->statep = state;
+
+    return JS_TRUE;
+}
+
+static JSBool
+PopState(JSONParser *jp)
+{
+    jp->statep--;
+    if (jp->statep < jp->stateStack) {
+        jp->statep = jp->stateStack;
+        return JS_FALSE;
+    } 
+
+    if (*jp->statep == JSON_PARSE_STATE_INIT)
+        *jp->statep = JSON_PARSE_STATE_FINISHED;
+
+    return JS_TRUE;
+}
+
+static JSBool
+PushValue(JSContext *cx, JSONParser *jp, JSObject *parent, jsval value)
+{
+    JSAutoTempValueRooter tvr(cx, 1, &value);
+  
+    JSBool ok;
+    if (OBJ_IS_ARRAY(cx, parent)) {
+        jsuint len;
+        ok = JS_GetArrayLength(cx, parent, &len);
+        if (ok)
+            ok = JS_SetElement(cx, parent, len, &value);
+    } else {
+        ok = JS_DefineUCProperty(cx, parent, JS_GetStringChars(jp->objectKey),
+                                 JS_GetStringLength(jp->objectKey), value,
+                                 NULL, NULL, JSPROP_ENUMERATE);
+    }
+
+    return ok;
+}
+
+static JSBool
+PushObject(JSContext *cx, JSONParser *jp, JSObject *obj)
+{
+    jsuint len;
+    if (!JS_GetArrayLength(cx, jp->objectStack, &len))
+        return JS_FALSE;
+    if (len >= JSON_MAX_DEPTH)
+        return JS_FALSE; // decoding error
+
+    jsval v = OBJECT_TO_JSVAL(obj);
+
+    // Check if this is the root object
+    if (len == 0) {
+        *jp->rootVal = v;        
+        if (!JS_SetElement(cx, jp->objectStack, 0, jp->rootVal))
+            return JS_FALSE;
+        return JS_TRUE;
+    }
+
+    jsval p;
+    if (!JS_GetElement(cx, jp->objectStack, len - 1, &p))
+        return JS_FALSE;
+    JS_ASSERT(JSVAL_IS_OBJECT(p));
+    JSObject *parent = JSVAL_TO_OBJECT(p);
+    if (!PushValue(cx, jp, parent, OBJECT_TO_JSVAL(obj)))
+        return JS_FALSE;
+
+    if (!JS_SetElement(cx, jp->objectStack, len, &v))
+        return JS_FALSE;
+
+    return JS_TRUE;
+}
+
+static JSBool
+OpenObject(JSContext *cx, JSONParser *jp)
+{
+    JSObject *obj = JS_NewObject(cx, NULL, NULL, NULL);
+    if (!obj)
+        return JS_FALSE;
+
+    return PushObject(cx, jp, obj);
+}
+
+static JSBool
+OpenArray(JSContext *cx, JSONParser *jp)
+{
+    // Add an array to an existing array or object
+    JSObject *arr = JS_NewArrayObject(cx, 0, NULL);
+    if (!arr)
+        return JS_FALSE;
+
+    return PushObject(cx, jp, arr);
+}
+
+static JSBool
+CloseObject(JSContext *cx, JSONParser *jp)
+{
+    jsuint len;
+    if (!JS_GetArrayLength(cx, jp->objectStack, &len))
+        return JS_FALSE;
+    if (!JS_SetArrayLength(cx, jp->objectStack, len - 1))
+        return JS_FALSE;
+
+    return JS_TRUE;
+}
+
+static JSBool
+CloseArray(JSContext *cx, JSONParser *jp)
+{
+  return CloseObject(cx, jp);
+}
+
+static JSBool
+HandleNumber(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len)
+{
+    JSBool ok;
+    jsuint length;
+    if (!JS_GetArrayLength(cx, jp->objectStack, &length))
+        return JS_FALSE;
+
+    jsval o;
+    if (!JS_GetElement(cx, jp->objectStack, length - 1, &o))
+        return JS_FALSE;
+    JS_ASSERT(JSVAL_IS_OBJECT(o));
+    JSObject *obj = JSVAL_TO_OBJECT(o);
+
+    const jschar *ep;
+    double val;    
+    if (!js_strtod(cx, buf, buf + len, &ep, &val) || ep != buf + len)
+        return JS_FALSE;
+
+    jsval numVal;
+    if (JS_NewNumberValue(cx, val, &numVal))
+        ok = PushValue(cx, jp, obj, numVal);
+    else
+        ok = JS_FALSE; // decode error
+
+    return ok;
+}
+
+static JSBool
+HandleString(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len)
+{
+    jsuint length;
+    if (!JS_GetArrayLength(cx, jp->objectStack, &length))
+        return JS_FALSE;
+
+    jsval o;
+    if (!JS_GetElement(cx, jp->objectStack, length - 1, &o))
+        return JS_FALSE;
+    JS_ASSERT(JSVAL_IS_OBJECT(o));
+    JSObject *obj = JSVAL_TO_OBJECT(o);
+
+    JSString *str = JS_NewUCStringCopyN(cx, buf, len);
+    if (!str)
+        return JS_FALSE;
+
+    return PushValue(cx, jp, obj, STRING_TO_JSVAL(str));
+}
+
+static JSBool
+HandleKeyword(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len)
+{
+    jsval keyword;
+    JSTokenType tt = js_CheckKeyword(buf, len);
+    if (tt != TOK_PRIMARY)
+        return JS_FALSE;
+
+    if (buf[0] == 'n')
+        keyword = JSVAL_NULL;
+    else if (buf[0] == 't')
+        keyword = JSVAL_TRUE;
+    else if (buf[0] == 'f')
+        keyword = JSVAL_FALSE;
+    else
+        return JS_FALSE;
+
+    jsuint length;
+    if (!JS_GetArrayLength(cx, jp->objectStack, &length))
+        return JS_FALSE;
+
+    jsval o;
+    if (!JS_GetElement(cx, jp->objectStack, length - 1, &o))
+        return JS_FALSE;
+    JS_ASSERT(JSVAL_IS_OBJECT(o));
+    JSObject *obj = JSVAL_TO_OBJECT(o);
+
+    return PushValue(cx, jp, obj, keyword);
+}
+
+static JSBool
+HandleData(JSContext *cx, JSONParser *jp, JSONDataType type, const jschar *buf, uint32 len)
+{
+  JSBool ok = JS_FALSE;
+
+  switch (type) {
+    case JSON_DATA_STRING:
+      ok = HandleString(cx, jp, buf, len);
+      break;
+
+    case JSON_DATA_KEYSTRING:
+      jp->objectKey = JS_NewUCStringCopyN(cx, buf, len);
+      ok = JS_TRUE;
+      break;
+
+    case JSON_DATA_NUMBER:
+      ok = HandleNumber(cx, jp, buf, len);
+      break;
+
+    case JSON_DATA_KEYWORD:
+      ok = HandleKeyword(cx, jp, buf, len);
+      break;
+
+    default:
+      JS_NOT_REACHED("Should have a JSON data type");
+  }
+
+  js_FinishStringBuffer(jp->buffer);
+  js_InitStringBuffer(jp->buffer);
+
+  return ok;
+}
+
+JSBool
+js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len)
+{
+    uint32 i;
+
+    if (*jp->statep == JSON_PARSE_STATE_INIT) {
+        PushState(jp, JSON_PARSE_STATE_OBJECT_VALUE);
+    }
+
+    for (i = 0; i < len; i++) {        
+        jschar c = data[i];
+        switch (*jp->statep) {
+            case JSON_PARSE_STATE_VALUE :
+                if (c == ']') {
+                    // empty array
+                    if (!PopState(jp))
+                        return JS_FALSE;
+                    if (*jp->statep != JSON_PARSE_STATE_ARRAY) 
+                        return JS_FALSE; // unexpected char
+                    if (!CloseArray(cx, jp) || !PopState(jp))
+                        return JS_FALSE;
+                    break;
+                }
+
+                if (c == '}') {
+                    // we should only find these in OBJECT_KEY state
+                    return JS_FALSE; // unexpected failure
+                }
+
+                if (c == '"') {
+                    *jp->statep = JSON_PARSE_STATE_STRING;
+                    break;
+                } 
+
+                if (IsNumChar(c)) {
+                    *jp->statep = JSON_PARSE_STATE_NUMBER;
+                    js_AppendChar(jp->buffer, c);
+                    break;
+                }
+
+                if (JS7_ISLET(c)) {
+                    *jp->statep = JSON_PARSE_STATE_KEYWORD;
+                    js_AppendChar(jp->buffer, c);
+                    break;
+                }
+
+            // fall through in case the value is an object or array
+            case JSON_PARSE_STATE_OBJECT_VALUE :
+                if (c == '{') {
+                  *jp->statep = JSON_PARSE_STATE_OBJECT;
+                  if (!OpenObject(cx, jp) || !PushState(jp, JSON_PARSE_STATE_OBJECT_PAIR))
+                      return JS_FALSE;
+                } else if (c == '[') {
+                  *jp->statep = JSON_PARSE_STATE_ARRAY;
+                  if (!OpenArray(cx, jp) || !PushState(jp, JSON_PARSE_STATE_VALUE))
+                      return JS_FALSE;
+                } else if (!JS_ISXMLSPACE(c)) {
+                  return JS_FALSE; // unexpected
+                }
+                break;
+
+            case JSON_PARSE_STATE_OBJECT :
+                if (c == '}') {                    
+                    if (!CloseObject(cx, jp) || !PopState(jp))
+                        return JS_FALSE;
+                } else if (c == ',') {
+                    if (!PushState(jp, JSON_PARSE_STATE_OBJECT_PAIR))
+                        return JS_FALSE;
+                } else if (c == ']' || !JS_ISXMLSPACE(c)) {
+                    return JS_FALSE; // unexpected
+                }
+                break;
+
+            case JSON_PARSE_STATE_ARRAY :
+                if (c == ']') {
+                    if (!CloseArray(cx, jp) || !PopState(jp))
+                        return JS_FALSE;
+                } else if (c == ',') {
+                    if (!PushState(jp, JSON_PARSE_STATE_VALUE))
+                        return JS_FALSE;
+                } else if (!JS_ISXMLSPACE(c)) {
+                    return JS_FALSE; // unexpected
+                }
+                break;
+            case JSON_PARSE_STATE_OBJECT_PAIR :
+                if (c == '"') {
+                    // we want to be waiting for a : when the string has been read
+                    *jp->statep = JSON_PARSE_STATE_OBJECT_IN_PAIR;
+                    if (!PushState(jp, JSON_PARSE_STATE_STRING))
+                        return JS_FALSE;
+                } else if (c == '}') {
+                    // pop off the object pair state and the object state
+                    if (!CloseObject(cx, jp) || !PopState(jp) || !PopState(jp))
+                        return JS_FALSE;
+                } else if (c == ']' || !JS_ISXMLSPACE(c)) {
+                  return JS_FALSE; // unexpected
+                }
+                break;
+            case JSON_PARSE_STATE_OBJECT_IN_PAIR:
+                if (c == ':') {
+                    *jp->statep = JSON_PARSE_STATE_VALUE;
+                } else if (!JS_ISXMLSPACE(c)) {
+                    return JS_FALSE; // unexpected
+                }
+                break;
+            case JSON_PARSE_STATE_STRING:
+                if (c == '"') {
+                    if (!PopState(jp))
+                        return JS_FALSE;
+                    JSONDataType jdt;
+                    if (*jp->statep == JSON_PARSE_STATE_OBJECT_IN_PAIR) {
+                        jdt = JSON_DATA_KEYSTRING;
+                    } else {
+                        jdt = JSON_DATA_STRING;
+                    }
+                    if (!HandleData(cx, jp, jdt, jp->buffer->base, STRING_BUFFER_OFFSET(jp->buffer)))
+                        return JS_FALSE;
+                } else if (c == '\\') {
+                    *jp->statep = JSON_PARSE_STATE_STRING_ESCAPE;
+                } else {
+                    js_AppendChar(jp->buffer, 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') {
+                            jp->numHex = 0;
+                            jp->hexChar = 0;
+                            *jp->statep = JSON_PARSE_STATE_STRING_HEX;
+                            continue;
+                        } else {
+                            return JS_FALSE; // unexpected
+                        }
+                }
+
+                js_AppendChar(jp->buffer, c);
+                *jp->statep = JSON_PARSE_STATE_STRING;
+                break;
+            case JSON_PARSE_STATE_STRING_HEX:
+                if (('0' <= c) && (c <= '9'))
+                  jp->hexChar = (jp->hexChar << 4) | (c - '0');
+                else if (('a' <= c) && (c <= 'f'))
+                  jp->hexChar = (jp->hexChar << 4) | (c - 'a' + 0x0a);
+                else if (('A' <= c) && (c <= 'F'))
+                  jp->hexChar = (jp->hexChar << 4) | (c - 'A' + 0x0a);
+                else
+                  return JS_FALSE; // unexpected
+
+                if (++(jp->numHex) == 4) {
+                    js_AppendChar(jp->buffer, jp->hexChar);
+                    jp->hexChar = 0;
+                    jp->numHex = 0;
+                    *jp->statep = JSON_PARSE_STATE_STRING;
+                }
+                break;
+            case JSON_PARSE_STATE_KEYWORD:
+                if (JS7_ISLET(c)) {
+                    js_AppendChar(jp->buffer, c);
+                } else {
+                    // this character isn't part of the keyword, process it again
+                    i--;
+                    if(!PopState(jp))
+                        return JS_FALSE;
+                
+                    if (!HandleData(cx, jp, JSON_DATA_KEYWORD, jp->buffer->base, STRING_BUFFER_OFFSET(jp->buffer)))
+                        return JS_FALSE;
+                }
+                break;
+            case JSON_PARSE_STATE_NUMBER:
+                if (IsNumChar(c)) {
+                    js_AppendChar(jp->buffer, c);
+                } else {
+                    // this character isn't part of the number, process it again
+                    i--;
+                    if(!PopState(jp))
+                        return JS_FALSE;
+                    if (!HandleData(cx, jp, JSON_DATA_NUMBER, jp->buffer->base, STRING_BUFFER_OFFSET(jp->buffer)))
+                        return JS_FALSE;
+                }
+                break;
+            case JSON_PARSE_STATE_FINISHED:
+                if (!JS_ISXMLSPACE(c))
+                  return JS_FALSE; // extra input
+
+                break;
+            default:
+                JS_NOT_REACHED("Invalid JSON parser state");
+      }
+    }
+
+    return JS_TRUE;
+}
+
+#if JS_HAS_TOSOURCE
+static JSBool
+json_toSource(JSContext *cx, uintN argc, jsval *vp)
+{
+    *vp = ATOM_KEY(CLASS_ATOM(cx, JSON));
+    return JS_TRUE;
+}
+#endif
+
+static JSFunctionSpec json_static_methods[] = {
+#if JS_HAS_TOSOURCE
+    JS_FN(js_toSource_str,  json_toSource,      0, 0),
+#endif
+    JS_FN("parse",          js_json_parse,      0, 0),
+    JS_FN("stringify",      js_json_stringify,  0, 0),
+    JS_FS_END
+};
+
+JSObject *
+js_InitJSONClass(JSContext *cx, JSObject *obj)
+{
+    JSObject *JSON;
+
+    JSON = JS_NewObject(cx, &js_JSONClass, NULL, obj);
+    if (!JSON)
+        return NULL;
+    if (!JS_DefineProperty(cx, obj, js_JSON_str, OBJECT_TO_JSVAL(JSON),
+                           JS_PropertyStub, JS_PropertyStub, JSPROP_ENUMERATE))
+        return NULL;
+
+    if (!JS_DefineFunctions(cx, JSON, json_static_methods))
+        return NULL;
+
+    return JSON;
+}
new file mode 100644
--- /dev/null
+++ b/js/src/json.h
@@ -0,0 +1,107 @@
+/* ***** 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 SpiderMonkey JSON.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998-1999
+ * 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 ***** */
+
+#ifndef json_h___
+#define json_h___
+/*
+ * JS JSON functions.
+ */
+
+#define JSON_MAX_DEPTH  2048
+#define JSON_PARSER_BUFSIZE 1024
+
+JS_BEGIN_EXTERN_C
+
+extern JSClass js_JSONClass;
+
+extern JSObject *
+js_InitJSONClass(JSContext *cx, JSObject *obj);
+
+extern JSBool
+js_Stringify(JSContext *cx, jsval *vp, JSObject *replacer,
+             JSONWriteCallback callback, void *data, uint32 depth);
+
+extern JSBool js_TryJSON(JSContext *cx, jsval *vp);
+
+enum JSONParserState {
+    JSON_PARSE_STATE_INIT,
+    JSON_PARSE_STATE_OBJECT_VALUE,
+    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
+};
+
+enum JSONDataType {
+    JSON_DATA_STRING,
+    JSON_DATA_KEYSTRING,
+    JSON_DATA_NUMBER,
+    JSON_DATA_KEYWORD
+};
+
+struct JSONParser {
+    /* Used while handling \uNNNN in strings */
+    jschar hexChar;
+    uint8 numHex;
+
+    JSONParserState *statep;
+    JSONParserState stateStack[JSON_MAX_DEPTH];
+    jsval *rootVal;
+    JSString *objectKey;
+    JSStringBuffer *buffer;
+    JSObject *objectStack;
+};
+
+extern JSONParser *
+js_BeginJSONParse(JSContext *cx, jsval *rootVal);
+
+extern JSBool
+js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len);
+
+extern JSBool
+js_FinishJSONParse(JSContext *cx, JSONParser *jp);
+
+JS_END_EXTERN_C
+
+#endif /* json_h___ */
--- a/js/src/jsproto.tbl
+++ b/js/src/jsproto.tbl
@@ -111,16 +111,17 @@ JS_PROTO(URIError,              24,     
 JS_PROTO(Generator,             25,     GENERATOR_INIT)
 JS_PROTO(Iterator,              26,     js_InitIteratorClasses)
 JS_PROTO(StopIteration,         27,     js_InitIteratorClasses)
 JS_PROTO(UnusedProto28,         28,     js_InitNullClass)
 JS_PROTO(File,                  29,     FILE_INIT)
 JS_PROTO(Block,                 30,     js_InitBlockClass)
 JS_PROTO(XMLFilter,             31,     XMLFILTER_INIT)
 JS_PROTO(NoSuchMethod,          32,     NO_SUCH_METHOD_INIT)
+JS_PROTO(JSON,                  33,     js_InitJSONClass)
 
 #undef SCRIPT_INIT
 #undef XML_INIT
 #undef NAMESPACE_INIT
 #undef QNAME_INIT
 #undef ANYNAME_INIT
 #undef ATTRIBUTE_INIT
 #undef GENERATOR_INIT
--- a/js/src/jspubtd.h
+++ b/js/src/jspubtd.h
@@ -149,16 +149,17 @@ typedef struct JSRuntime         JSRunti
 typedef struct JSRuntime         JSTaskState;   /* XXX deprecated name */
 typedef struct JSScript          JSScript;
 typedef struct JSStackFrame      JSStackFrame;
 typedef struct JSString          JSString;
 typedef struct JSXDRState        JSXDRState;
 typedef struct JSExceptionState  JSExceptionState;
 typedef struct JSLocaleCallbacks JSLocaleCallbacks;
 typedef struct JSSecurityCallbacks JSSecurityCallbacks;
+typedef struct JSONParser        JSONParser;
 
 /* JSClass (and JSObjectOps where appropriate) function pointer typedefs. */
 
 /*
  * Add, delete, get or set a property named by id in obj.  Note the jsval id
  * type -- id may be a string (Unicode property identifier) or an int (element
  * index).  The *vp out parameter, on success, is the new property value after
  * an add, get, or set.  After a successful delete, *vp is JSVAL_FALSE iff