Backed out 4 changesets (bug 1347515) for causing bug 1416468. DEVEDITION_58_0b3_RELEASE FIREFOX_58_0b3_RELEASE
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 13 Nov 2017 22:28:14 -0500
changeset 442288 f7eb3bbcd5f830244a4234b1269c3a73e750330b
parent 442287 892f43fed15c45f156bd39924e19fde74bb0e9c9
child 442289 53e967aa16cb26a135b30c98d4a4ee7947cef2eb
child 442294 03402f586daaea0c5dbc80c281d262db9c9de430
push id8149
push userryanvm@gmail.com
push dateTue, 14 Nov 2017 03:28:31 +0000
treeherdermozilla-beta@f7eb3bbcd5f8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1347515, 1416468
milestone58.0
backs out31b7eb1946621744296146e50b719b326dde10c9
08f21a0e7ebebf714060f31650025244f993ac89
e26f2365ea7c7ec993d1d514a26c278ff381b43a
a3c81ef737006a74b0b7d23e85867f73ba6de144
Backed out 4 changesets (bug 1347515) for causing bug 1416468. Backed out changeset 31b7eb194662 (bug 1347515) Backed out changeset 08f21a0e7ebe (bug 1347515) Backed out changeset e26f2365ea7c (bug 1347515) Backed out changeset a3c81ef73700 (bug 1347515)
browser/installer/package-manifest.in
dom/base/nsDocument.cpp
dom/interfaces/json/moz.build
dom/interfaces/json/nsIJSON.idl
dom/json/moz.build
dom/json/nsJSON.cpp
dom/json/nsJSON.h
dom/json/test/mochitest.ini
dom/json/test/test_json.html
dom/json/test/unit/decodeFromStream-01.json
dom/json/test/unit/decodeFromStream-small.json
dom/json/test/unit/test_decodeFromStream.js
dom/json/test/unit/xpcshell.ini
dom/moz.build
js/xpconnect/tests/unit/test_isModuleLoaded.js
layout/build/moz.build
layout/build/nsLayoutModule.cpp
mobile/android/installer/package-manifest.in
services/sync/tests/unit/head_helpers.js
services/sync/tps/extensions/tps/resource/tps.jsm
testing/xpcshell/head.js
toolkit/components/contextualidentity/ContextualIdentityService.jsm
toolkit/components/search/nsSearchService.js
toolkit/components/search/tests/xpcshell/head_search.js
toolkit/components/telemetry/tests/unit/head.js
toolkit/modules/JSONFile.jsm
toolkit/mozapps/extensions/internal/XPIProvider.jsm
toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -202,16 +202,17 @@
 @RESPATH@/components/dom_css.xpt
 @RESPATH@/components/dom_events.xpt
 @RESPATH@/components/dom_geolocation.xpt
 @RESPATH@/components/dom_media.xpt
 @RESPATH@/components/dom_network.xpt
 @RESPATH@/components/dom_notification.xpt
 @RESPATH@/components/dom_html.xpt
 @RESPATH@/components/dom_offline.xpt
+@RESPATH@/components/dom_json.xpt
 @RESPATH@/components/dom_payments.xpt
 @RESPATH@/components/dom_power.xpt
 @RESPATH@/components/dom_push.xpt
 @RESPATH@/components/dom_quota.xpt
 @RESPATH@/components/dom_range.xpt
 @RESPATH@/components/dom_security.xpt
 @RESPATH@/components/dom_sidebar.xpt
 @RESPATH@/components/dom_storage.xpt
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -86,16 +86,17 @@
 #include "nsIServiceManager.h"
 #include "mozilla/dom/workers/ServiceWorkerManager.h"
 #include "imgLoader.h"
 
 #include "nsCanvasFrame.h"
 #include "nsContentCID.h"
 #include "nsError.h"
 #include "nsPresContext.h"
+#include "nsIJSON.h"
 #include "nsThreadUtils.h"
 #include "nsNodeInfoManager.h"
 #include "nsIFileChannel.h"
 #include "nsIMultiPartChannel.h"
 #include "nsIRefreshURI.h"
 #include "nsIWebNavigation.h"
 #include "nsIScriptError.h"
 #include "nsISimpleEnumerator.h"
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/json/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+    BUG_COMPONENT = ("Core", "JavaScript Engine")
+
+XPIDL_SOURCES += [
+    'nsIJSON.idl',
+]
+
+XPIDL_MODULE = 'dom_json'
+
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/json/nsIJSON.idl
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "domstubs.idl"
+
+interface nsIInputStream;
+interface nsIScriptGlobalObject;
+
+[ptr] native JSValPtr(JS::Value);
+[ptr] native JSContext(JSContext);
+
+%{C++
+#include "js/TypeDecls.h"
+%}
+
+/**
+ * Don't use this!  Use JSON.parse and JSON.stringify directly.
+ */
+[scriptable, uuid(083aebb0-7790-43b2-ae81-9e404e626236)]
+interface nsIJSON : nsISupports
+{
+  [implicit_jscontext]
+  jsval decodeFromStream(in nsIInputStream stream,
+                         in long contentLength);
+
+  [noscript] AString  encodeFromJSVal(in JSValPtr value, in JSContext cx);
+
+  // Make sure you GCroot the result of this function before using it.
+  [noscript] jsval    decodeToJSVal(in AString str, in JSContext cx);
+};
new file mode 100644
--- /dev/null
+++ b/dom/json/moz.build
@@ -0,0 +1,25 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+    BUG_COMPONENT = ("Core", "JavaScript Engine")
+
+EXPORTS += [
+    'nsJSON.h',
+]
+
+UNIFIED_SOURCES += [
+    'nsJSON.cpp',
+]
+
+LOCAL_INCLUDES += [
+    '/dom/base',
+]
+
+FINAL_LIBRARY = 'xul'
+
+XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
+MOCHITEST_MANIFESTS += ['test/mochitest.ini']
new file mode 100644
--- /dev/null
+++ b/dom/json/nsJSON.cpp
@@ -0,0 +1,420 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsJSON.h"
+
+#include "jsapi.h"
+#include "js/CharacterEncoding.h"
+#include "nsIXPConnect.h"
+#include "nsIXPCScriptable.h"
+#include "nsStreamUtils.h"
+#include "nsIInputStream.h"
+#include "nsStringStream.h"
+#include "nsNetUtil.h"
+#include "nsIURI.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsIScriptError.h"
+#include "nsCRTGlue.h"
+#include "nsIScriptSecurityManager.h"
+#include "NullPrincipal.h"
+#include "mozilla/Maybe.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+#define JSON_STREAM_BUFSIZE 4096
+
+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()
+{
+}
+
+static bool
+WriteCallback(const char16_t *buf, uint32_t len, void *data)
+{
+  nsJSONWriter *writer = static_cast<nsJSONWriter*>(data);
+  nsresult rv =  writer->Write((const char16_t*)buf, (uint32_t)len);
+  if (NS_FAILED(rv))
+    return false;
+
+  return true;
+}
+
+NS_IMETHODIMP
+nsJSON::EncodeFromJSVal(JS::Value *value, JSContext *cx, nsAString &result)
+{
+  result.Truncate();
+
+  mozilla::Maybe<JSAutoCompartment> ac;
+  if (value->isObject()) {
+    JS::Rooted<JSObject*> obj(cx, &value->toObject());
+    ac.emplace(cx, obj);
+  }
+
+  nsJSONWriter writer;
+  JS::Rooted<JS::Value> vp(cx, *value);
+  if (!JS_Stringify(cx, &vp, nullptr, JS::NullHandleValue, WriteCallback, &writer)) {
+    return NS_ERROR_XPC_BAD_CONVERT_JS;
+  }
+  *value = vp;
+
+  NS_ENSURE_TRUE(writer.DidWrite(), NS_ERROR_UNEXPECTED);
+  writer.FlushBuffer();
+  result.Assign(writer.mOutputString);
+  return NS_OK;
+}
+
+
+nsJSONWriter::nsJSONWriter() : mStream(nullptr),
+                               mBuffer(nullptr),
+                               mBufferCount(0),
+                               mDidWrite(false),
+                               mEncoder(nullptr)
+{
+}
+
+nsJSONWriter::nsJSONWriter(nsIOutputStream* aStream)
+  : mStream(aStream)
+  , mBuffer(nullptr)
+  , mBufferCount(0)
+  , mDidWrite(false)
+  , mEncoder(UTF_8_ENCODING->NewEncoder())
+{
+}
+
+nsJSONWriter::~nsJSONWriter()
+{
+  delete [] mBuffer;
+}
+
+nsresult
+nsJSONWriter::Write(const char16_t *aBuffer, uint32_t aLength)
+{
+  if (mStream) {
+    return WriteToStream(mStream, mEncoder.get(), aBuffer, aLength);
+  }
+
+  if (!mDidWrite) {
+    mBuffer = new char16_t[JSON_STREAM_BUFSIZE];
+    mDidWrite = true;
+  }
+
+  if (JSON_STREAM_BUFSIZE <= aLength + mBufferCount) {
+    mOutputString.Append(mBuffer, mBufferCount);
+    mBufferCount = 0;
+  }
+
+  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(char16_t));
+    mBufferCount += aLength;
+  }
+
+  return NS_OK;
+}
+
+bool nsJSONWriter::DidWrite()
+{
+  return mDidWrite;
+}
+
+void
+nsJSONWriter::FlushBuffer()
+{
+  mOutputString.Append(mBuffer, mBufferCount);
+}
+
+nsresult
+nsJSONWriter::WriteToStream(nsIOutputStream* aStream,
+                            Encoder* encoder,
+                            const char16_t* aBuffer,
+                            uint32_t aLength)
+{
+  uint8_t buffer[1024];
+  auto dst = MakeSpan(buffer);
+  auto src = MakeSpan(aBuffer, aLength);
+
+  for (;;) {
+    uint32_t result;
+    size_t read;
+    size_t written;
+    bool hadErrors;
+    Tie(result, read, written, hadErrors) =
+      encoder->EncodeFromUTF16(src, dst, false);
+    Unused << hadErrors;
+    src = src.From(read);
+    uint32_t ignored;
+    nsresult rv =
+      aStream->Write(reinterpret_cast<const char*>(buffer), written, &ignored);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+    if (result == kInputEmpty) {
+      mDidWrite = true;
+      return NS_OK;
+    }
+  }
+}
+
+NS_IMETHODIMP
+nsJSON::DecodeFromStream(nsIInputStream *aStream, int32_t aContentLength,
+                         JSContext* cx, JS::MutableHandle<JS::Value> aRetval)
+{
+  return DecodeInternal(cx, aStream, aContentLength, true, aRetval);
+}
+
+NS_IMETHODIMP
+nsJSON::DecodeToJSVal(const nsAString &str, JSContext *cx,
+                      JS::MutableHandle<JS::Value> result)
+{
+  if (!JS_ParseJSON(cx, static_cast<const char16_t*>(PromiseFlatString(str).get()),
+                    str.Length(), result)) {
+    return NS_ERROR_UNEXPECTED;
+  }
+  return NS_OK;
+}
+
+nsresult
+nsJSON::DecodeInternal(JSContext* cx,
+                       nsIInputStream *aStream,
+                       int32_t aContentLength,
+                       bool aNeedsConverter,
+                       JS::MutableHandle<JS::Value> aRetval)
+{
+  // Consume the stream
+  nsCOMPtr<nsIChannel> jsonChannel;
+  if (!mURI) {
+    NS_NewURI(getter_AddRefs(mURI), NS_LITERAL_CSTRING("about:blank"), 0, 0 );
+    if (!mURI)
+      return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  nsresult rv;
+  nsCOMPtr<nsIPrincipal> nullPrincipal = NullPrincipal::Create();
+
+  // The ::Decode function is deprecated [Bug 675797] and the following
+  // channel is never openend, so it does not matter what securityFlags
+  // we pass to NS_NewInputStreamChannel here.
+  rv = NS_NewInputStreamChannel(getter_AddRefs(jsonChannel),
+                                mURI,
+                                aStream,
+                                nullPrincipal,
+                                nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
+                                nsIContentPolicy::TYPE_OTHER,
+                                NS_LITERAL_CSTRING("application/json"));
+
+  if (!jsonChannel || NS_FAILED(rv))
+    return NS_ERROR_FAILURE;
+
+  RefPtr<nsJSONListener> jsonListener =
+    new nsJSONListener(cx, aRetval.address(), aNeedsConverter);
+
+  //XXX this stream pattern should be consolidated in netwerk
+  rv = jsonListener->OnStartRequest(jsonChannel, nullptr);
+  if (NS_FAILED(rv)) {
+    jsonChannel->Cancel(rv);
+    return rv;
+  }
+
+  nsresult status;
+  jsonChannel->GetStatus(&status);
+  uint64_t offset = 0;
+  while (NS_SUCCEEDED(status)) {
+    uint64_t 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
+
+    if (available > UINT32_MAX)
+      available = UINT32_MAX;
+
+    rv = jsonListener->OnDataAvailable(jsonChannel, nullptr,
+                                       aStream,
+                                       offset,
+                                       (uint32_t)available);
+    if (NS_FAILED(rv)) {
+      jsonChannel->Cancel(rv);
+      break;
+    }
+
+    offset += available;
+    jsonChannel->GetStatus(&status);
+  }
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = jsonListener->OnStopRequest(jsonChannel, nullptr, status);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+NS_NewJSON(nsISupports* aOuter, REFNSIID aIID, void** aResult)
+{
+  nsJSON* json = new nsJSON();
+  NS_ADDREF(json);
+  *aResult = json;
+
+  return NS_OK;
+}
+
+nsJSONListener::nsJSONListener(JSContext *cx, JS::Value *rootVal,
+                               bool needsConverter)
+  : mNeedsConverter(needsConverter),
+    mCx(cx),
+    mRootVal(rootVal)
+{
+}
+
+nsJSONListener::~nsJSONListener()
+{
+}
+
+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)
+{
+  mDecoder = nullptr;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJSONListener::OnStopRequest(nsIRequest *aRequest, nsISupports *aContext,
+                              nsresult aStatusCode)
+{
+  JS::Rooted<JS::Value> reviver(mCx, JS::NullValue()), value(mCx);
+
+  JS::ConstTwoByteChars chars(reinterpret_cast<const char16_t*>(mBufferedChars.Elements()),
+                              mBufferedChars.Length());
+  bool ok = JS_ParseJSONWithReviver(mCx, chars.begin().get(),
+                                      uint32_t(mBufferedChars.Length()),
+                                      reviver, &value);
+
+  *mRootVal = value;
+  mBufferedChars.TruncateLength(0);
+  return ok ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJSONListener::OnDataAvailable(nsIRequest *aRequest, nsISupports *aContext,
+                                nsIInputStream *aStream,
+                                uint64_t aOffset, uint32_t aLength)
+{
+  nsresult rv = NS_OK;
+
+  char buffer[JSON_STREAM_BUFSIZE];
+  unsigned long bytesRemaining = aLength;
+  while (bytesRemaining) {
+    unsigned int bytesRead;
+    rv = aStream->Read(buffer,
+                       std::min((unsigned long)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, uint32_t aByteLength)
+{
+  if (mNeedsConverter && !mDecoder) {
+    // BOM sniffing is built into the decoder.
+    mDecoder = UTF_8_ENCODING->NewDecoder();
+  }
+
+  if (!aBuffer)
+    return NS_OK;
+
+  nsresult rv;
+  if (mNeedsConverter) {
+    rv = ConsumeConverted(aBuffer, aByteLength);
+  } else {
+    uint32_t unichars = aByteLength / sizeof(char16_t);
+    rv = Consume((char16_t *) aBuffer, unichars);
+  }
+
+  return rv;
+}
+
+nsresult
+nsJSONListener::ConsumeConverted(const char* aBuffer, uint32_t aByteLength)
+{
+  CheckedInt<size_t> needed = mDecoder->MaxUTF16BufferLength(aByteLength);
+  if (!needed.isValid()) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  CheckedInt<size_t> total(needed);
+  total += mBufferedChars.Length();
+  if (!total.isValid()) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  char16_t* endelems = mBufferedChars.AppendElements(needed.value(), fallible);
+  if (!endelems) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  auto src = AsBytes(MakeSpan(aBuffer, aByteLength));
+  auto dst = MakeSpan(endelems, needed.value());
+  uint32_t result;
+  size_t read;
+  size_t written;
+  bool hadErrors;
+  // Ignoring EOF like the old code
+  Tie(result, read, written, hadErrors) =
+    mDecoder->DecodeToUTF16(src, dst, false);
+  MOZ_ASSERT(result == kInputEmpty);
+  MOZ_ASSERT(read == src.Length());
+  MOZ_ASSERT(written <= needed.value());
+  Unused << hadErrors;
+  mBufferedChars.TruncateLength(total.value() - (needed.value() - written));
+  return NS_OK;
+}
+
+nsresult
+nsJSONListener::Consume(const char16_t* aBuffer, uint32_t aByteLength)
+{
+  if (!mBufferedChars.AppendElements(aBuffer, aByteLength))
+    return NS_ERROR_FAILURE;
+
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/json/nsJSON.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsJSON_h__
+#define nsJSON_h__
+
+#include "nsIJSON.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIOutputStream.h"
+#include "mozilla/Encoding.h"
+#include "nsIRequestObserver.h"
+#include "nsIStreamListener.h"
+#include "nsTArray.h"
+
+class nsIURI;
+
+class MOZ_STACK_CLASS nsJSONWriter
+{
+public:
+  nsJSONWriter();
+  explicit nsJSONWriter(nsIOutputStream* aStream);
+  virtual ~nsJSONWriter();
+  nsCOMPtr<nsIOutputStream> mStream;
+  nsresult Write(const char16_t *aBuffer, uint32_t aLength);
+  nsString mOutputString;
+  bool DidWrite();
+  void FlushBuffer();
+
+protected:
+  char16_t *mBuffer;
+  uint32_t mBufferCount;
+  bool mDidWrite;
+  nsresult WriteToStream(nsIOutputStream* aStream,
+                         mozilla::Encoder* encoder,
+                         const char16_t* aBuffer,
+                         uint32_t aLength);
+
+  mozilla::UniquePtr<mozilla::Encoder> mEncoder;
+};
+
+class nsJSON final : public nsIJSON
+{
+public:
+  nsJSON();
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIJSON
+
+protected:
+  virtual ~nsJSON();
+
+  nsresult DecodeInternal(JSContext* cx,
+                          nsIInputStream* aStream,
+                          int32_t aContentLength,
+                          bool aNeedsConverter,
+                          JS::MutableHandle<JS::Value> aRetVal);
+  nsCOMPtr<nsIURI> mURI;
+};
+
+nsresult
+NS_NewJSON(nsISupports* aOuter, REFNSIID aIID, void** aResult);
+
+class nsJSONListener : public nsIStreamListener
+{
+public:
+  nsJSONListener(JSContext *cx, JS::Value *rootVal, bool needsConverter);
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIREQUESTOBSERVER
+  NS_DECL_NSISTREAMLISTENER
+
+protected:
+  virtual ~nsJSONListener();
+
+  bool mNeedsConverter;
+  JSContext *mCx;
+  JS::Value *mRootVal;
+  mozilla::UniquePtr<mozilla::Decoder> mDecoder;
+  nsTArray<char16_t> mBufferedChars;
+  nsresult ProcessBytes(const char* aBuffer, uint32_t aByteLength);
+  nsresult ConsumeConverted(const char* aBuffer, uint32_t aByteLength);
+  nsresult Consume(const char16_t *data, uint32_t len);
+};
+
+#endif
new file mode 100644
--- /dev/null
+++ b/dom/json/test/mochitest.ini
@@ -0,0 +1,1 @@
+[test_json.html]
new file mode 100644
--- /dev/null
+++ b/dom/json/test/test_json.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=408838
+-->
+<head>
+  <title>Test for Bug 408838</title>
+  <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>
+
new file mode 100644
--- /dev/null
+++ b/dom/json/test/unit/decodeFromStream-01.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/json/test/unit/decodeFromStream-small.json
@@ -0,0 +1,1 @@
+{}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/json/test/unit/test_decodeFromStream.js
@@ -0,0 +1,29 @@
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+
+var nativeJSON = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
+
+function run_test()
+{
+  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);
+      return nativeJSON.decodeFromStream(istream, istream.available());
+    }
+    finally
+    {
+      istream.close();
+    }
+  }
+
+  var x = read_file("decodeFromStream-01.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("decodeFromStream-small.json");
+  do_check_eq(x.toSource(), "({})", "empty object parsed");
+}
new file mode 100644
--- /dev/null
+++ b/dom/json/test/unit/xpcshell.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+head =
+support-files =
+  decodeFromStream-01.json
+  decodeFromStream-small.json
+
+[test_decodeFromStream.js]
+
--- a/dom/moz.build
+++ b/dom/moz.build
@@ -24,16 +24,17 @@ interfaces = [
     'css',
     'traversal',
     'range',
     'xbl',
     'xpath',
     'xul',
     'security',
     'storage',
+    'json',
     'offline',
     'geolocation',
     'notification',
     'svg',
     'smil',
     'push',
     'payments',
 ]
@@ -59,16 +60,17 @@ DIRS += [
     'file',
     'filehandle',
     'filesystem',
     'flyweb',
     'gamepad',
     'geolocation',
     'grid',
     'html',
+    'json',
     'jsurl',
     'asmjscache',
     'mathml',
     'media',
     'notification',
     'offline',
     'power',
     'push',
--- a/js/xpconnect/tests/unit/test_isModuleLoaded.js
+++ b/js/xpconnect/tests/unit/test_isModuleLoaded.js
@@ -1,13 +1,13 @@
 const Cu = Components.utils;
 
 function run_test() {
   // Existing module.
-  do_check_true(Cu.isModuleLoaded("resource://gre/modules/NetUtil.jsm"),
+  do_check_true(!Cu.isModuleLoaded("resource://gre/modules/NetUtil.jsm"),
                 "isModuleLoaded returned correct value for non-loaded module");
   Cu.import("resource://gre/modules/NetUtil.jsm");
   do_check_true(Cu.isModuleLoaded("resource://gre/modules/NetUtil.jsm"),
                 "isModuleLoaded returned true after loading that module");
   Cu.unload("resource://gre/modules/NetUtil.jsm");
   do_check_true(!Cu.isModuleLoaded("resource://gre/modules/NetUtil.jsm"),
                 "isModuleLoaded returned false after unloading that module");
 
--- a/layout/build/moz.build
+++ b/layout/build/moz.build
@@ -32,16 +32,17 @@ LOCAL_INCLUDES += [
     '/docshell/base',
     '/dom/audiochannel',
     '/dom/base',
     '/dom/bindings',
     '/dom/canvas',
     '/dom/filesystem',
     '/dom/geolocation',
     '/dom/html',
+    '/dom/json',
     '/dom/jsurl',
     '/dom/media',
     '/dom/offline',
     '/dom/storage',
     '/dom/svg',
     '/dom/xbl',
     '/dom/xslt/base',
     '/dom/xslt/xml',
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -70,16 +70,17 @@
 
 #include "mozilla/dom/FormData.h"
 #include "nsHostObjectURI.h"
 #include "nsGlobalWindowCommands.h"
 #include "nsIControllerCommandTable.h"
 #include "nsJSProtocolHandler.h"
 #include "nsScriptNameSpaceManager.h"
 #include "nsIControllerContext.h"
+#include "nsJSON.h"
 #include "nsZipArchive.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/DOMException.h"
 #include "mozilla/dom/DOMRequest.h"
 #include "mozilla/dom/LocalStorageManager.h"
 #include "mozilla/dom/network/UDPSocketChild.h"
 #include "mozilla/dom/quota/QuotaManagerService.h"
 #include "mozilla/dom/SessionStorageManager.h"
@@ -623,16 +624,17 @@ NS_DEFINE_NAMED_CID(TRANSFORMIIX_XPATH_E
 NS_DEFINE_NAMED_CID(TRANSFORMIIX_NODESET_CID);
 NS_DEFINE_NAMED_CID(NS_XMLSERIALIZER_CID);
 NS_DEFINE_NAMED_CID(NS_FORMDATA_CID);
 NS_DEFINE_NAMED_CID(NS_HOSTOBJECTURI_CID);
 NS_DEFINE_NAMED_CID(NS_XMLHTTPREQUEST_CID);
 NS_DEFINE_NAMED_CID(NS_DOMPARSER_CID);
 NS_DEFINE_NAMED_CID(NS_DOMSESSIONSTORAGEMANAGER_CID);
 NS_DEFINE_NAMED_CID(NS_DOMLOCALSTORAGEMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_DOMJSON_CID);
 NS_DEFINE_NAMED_CID(NS_TEXTEDITOR_CID);
 NS_DEFINE_NAMED_CID(DOMREQUEST_SERVICE_CID);
 NS_DEFINE_NAMED_CID(QUOTAMANAGER_SERVICE_CID);
 NS_DEFINE_NAMED_CID(SERVICEWORKERMANAGER_CID);
 NS_DEFINE_NAMED_CID(NOTIFICATIONTELEMETRYSERVICE_CID);
 NS_DEFINE_NAMED_CID(PUSHNOTIFIER_CID);
 NS_DEFINE_NAMED_CID(WORKERDEBUGGERMANAGER_CID);
 NS_DEFINE_NAMED_CID(NS_AUDIOCHANNELAGENT_CID);
@@ -881,16 +883,17 @@ static const mozilla::Module::CIDEntry k
   { &kNS_XMLSERIALIZER_CID, false, nullptr, nsDOMSerializerConstructor },
   { &kNS_FORMDATA_CID, false, nullptr, FormDataConstructor },
   { &kNS_HOSTOBJECTURI_CID, false, nullptr, nsHostObjectURIConstructor },
   { &kNS_XMLHTTPREQUEST_CID, false, nullptr, XMLHttpRequestMainThreadConstructor },
   { &kNS_DOMPARSER_CID, false, nullptr, DOMParserConstructor },
   { &kNS_XPCEXCEPTION_CID, false, nullptr, ExceptionConstructor },
   { &kNS_DOMSESSIONSTORAGEMANAGER_CID, false, nullptr, SessionStorageManagerConstructor },
   { &kNS_DOMLOCALSTORAGEMANAGER_CID, false, nullptr, LocalStorageManagerConstructor },
+  { &kNS_DOMJSON_CID, false, nullptr, NS_NewJSON },
   { &kNS_TEXTEDITOR_CID, false, nullptr, TextEditorConstructor },
   { &kDOMREQUEST_SERVICE_CID, false, nullptr, DOMRequestServiceConstructor },
   { &kQUOTAMANAGER_SERVICE_CID, false, nullptr, QuotaManagerServiceConstructor },
   { &kSERVICEWORKERMANAGER_CID, false, nullptr, ServiceWorkerManagerConstructor },
   { &kNOTIFICATIONTELEMETRYSERVICE_CID, false, nullptr, NotificationTelemetryServiceConstructor },
   { &kPUSHNOTIFIER_CID, false, nullptr, PushNotifierConstructor },
   { &kWORKERDEBUGGERMANAGER_CID, true, nullptr, WorkerDebuggerManagerConstructor },
   { &kNS_AUDIOCHANNELAGENT_CID, true, nullptr, AudioChannelAgentConstructor },
@@ -1007,16 +1010,17 @@ static const mozilla::Module::ContractID
   { NS_FORMDATA_CONTRACTID, &kNS_FORMDATA_CID },
   { NS_XMLHTTPREQUEST_CONTRACTID, &kNS_XMLHTTPREQUEST_CID },
   { NS_DOMPARSER_CONTRACTID, &kNS_DOMPARSER_CID },
   { XPC_EXCEPTION_CONTRACTID, &kNS_XPCEXCEPTION_CID },
   { "@mozilla.org/dom/localStorage-manager;1", &kNS_DOMLOCALSTORAGEMANAGER_CID },
   // Keeping the old ContractID for backward compatibility
   { "@mozilla.org/dom/storagemanager;1", &kNS_DOMLOCALSTORAGEMANAGER_CID },
   { "@mozilla.org/dom/sessionStorage-manager;1", &kNS_DOMSESSIONSTORAGEMANAGER_CID },
+  { "@mozilla.org/dom/json;1", &kNS_DOMJSON_CID },
   { "@mozilla.org/editor/texteditor;1", &kNS_TEXTEDITOR_CID },
   { DOMREQUEST_SERVICE_CONTRACTID, &kDOMREQUEST_SERVICE_CID },
   { QUOTAMANAGER_SERVICE_CONTRACTID, &kQUOTAMANAGER_SERVICE_CID },
   { SERVICEWORKERMANAGER_CONTRACTID, &kSERVICEWORKERMANAGER_CID },
   { NOTIFICATIONTELEMETRYSERVICE_CONTRACTID, &kNOTIFICATIONTELEMETRYSERVICE_CID },
   { PUSHNOTIFIER_CONTRACTID, &kPUSHNOTIFIER_CID },
   { WORKERDEBUGGERMANAGER_CONTRACTID, &kWORKERDEBUGGERMANAGER_CID },
   { NS_AUDIOCHANNELAGENT_CONTRACTID, &kNS_AUDIOCHANNELAGENT_CID },
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -126,16 +126,17 @@
 @BINPATH@/components/dom_events.xpt
 @BINPATH@/components/dom_file.xpt
 @BINPATH@/components/dom_geolocation.xpt
 @BINPATH@/components/dom_media.xpt
 @BINPATH@/components/dom_network.xpt
 @BINPATH@/components/dom_notification.xpt
 @BINPATH@/components/dom_html.xpt
 @BINPATH@/components/dom_offline.xpt
+@BINPATH@/components/dom_json.xpt
 @BINPATH@/components/dom_payments.xpt
 @BINPATH@/components/dom_power.xpt
 #ifdef MOZ_ANDROID_GCM
 @BINPATH@/components/dom_push.xpt
 #endif
 @BINPATH@/components/dom_quota.xpt
 @BINPATH@/components/dom_range.xpt
 @BINPATH@/components/dom_security.xpt
--- a/services/sync/tests/unit/head_helpers.js
+++ b/services/sync/tests/unit/head_helpers.js
@@ -32,26 +32,25 @@ add_task(async function head_setup() {
 Cu.import("resource://gre/modules/Timer.jsm");
 Services.scriptloader.loadSubScript("resource://testing-common/sinon-2.3.2.js", this);
 /* globals sinon */
 // ================================================
 
 XPCOMUtils.defineLazyGetter(this, "SyncPingSchema", function() {
   let ns = {};
   Cu.import("resource://gre/modules/FileUtils.jsm", ns);
-  Cu.import("resource://gre/modules/NetUtil.jsm", ns);
   let stream = Cc["@mozilla.org/network/file-input-stream;1"]
                .createInstance(Ci.nsIFileInputStream);
+  let jsonReader = Cc["@mozilla.org/dom/json;1"]
+                   .createInstance(Components.interfaces.nsIJSON);
   let schema;
   try {
     let schemaFile = do_get_file("sync_ping_schema.json");
     stream.init(schemaFile, ns.FileUtils.MODE_RDONLY, ns.FileUtils.PERMS_FILE, 0);
-
-    let bytes = ns.NetUtil.readInputStream(stream, stream.available());
-    schema = JSON.parse((new TextDecoder()).decode(bytes));
+    schema = jsonReader.decodeFromStream(stream, stream.available());
   } finally {
     stream.close();
   }
 
   // Allow tests to make whatever engines they want, this shouldn't cause
   // validation failure.
   schema.definitions.engine.properties.name = { type: "string" };
   return schema;
--- a/services/sync/tps/extensions/tps/resource/tps.jsm
+++ b/services/sync/tps/extensions/tps/resource/tps.jsm
@@ -51,23 +51,16 @@ var hh = Cc["@mozilla.org/network/protoc
 var prefs = Cc["@mozilla.org/preferences-service;1"]
             .getService(Ci.nsIPrefBranch);
 
 XPCOMUtils.defineLazyGetter(this, "fileProtocolHandler", () => {
   let fileHandler = Services.io.getProtocolHandler("file");
   return fileHandler.QueryInterface(Ci.nsIFileProtocolHandler);
 });
 
-XPCOMUtils.defineLazyGetter(this, "gTextDecoder", () => {
-  return new TextDecoder();
-});
-
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
-                                  "resource://gre/modules/NetUtil.jsm");
-
 // Options for wiping data during a sync
 const SYNC_RESET_CLIENT = "resetClient";
 const SYNC_WIPE_CLIENT  = "wipeClient";
 const SYNC_WIPE_REMOTE  = "wipeRemote";
 
 // Actions a test can perform
 const ACTION_ADD                = "add";
 const ACTION_DELETE             = "delete";
@@ -796,20 +789,21 @@ var TPS = {
   _tryLoadPingSchema(testFile) {
     try {
       let schemaFile = this._getFileRelativeToSourceRoot(testFile,
         "services/sync/tests/unit/sync_ping_schema.json");
 
       let stream = Cc["@mozilla.org/network/file-input-stream;1"]
                    .createInstance(Ci.nsIFileInputStream);
 
-      stream.init(schemaFile, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
+      let jsonReader = Cc["@mozilla.org/dom/json;1"]
+                       .createInstance(Components.interfaces.nsIJSON);
 
-      let bytes = NetUtil.readInputStream(stream, stream.available());
-      let schema = JSON.parse(gTextDecoder.decode(bytes));
+      stream.init(schemaFile, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
+      let schema = jsonReader.decodeFromStream(stream, stream.available());
       Logger.logInfo("Successfully loaded schema");
 
       // Importing resource://testing-common/* isn't possible from within TPS,
       // so we load Ajv manually.
       let ajvFile = this._getFileRelativeToSourceRoot(testFile, "testing/modules/ajv-4.1.1.js");
       let ajvURL = fileProtocolHandler.getURLSpecFromFile(ajvFile);
       let ns = {};
       Cu.import(ajvURL, ns);
--- a/testing/xpcshell/head.js
+++ b/testing/xpcshell/head.js
@@ -35,19 +35,16 @@ var _XPCSHELL_PROCESS;
 
 // Register the testing-common resource protocol early, to have access to its
 // modules.
 _register_modules_protocol_handler();
 
 var _Promise = Components.utils.import("resource://gre/modules/Promise.jsm", {}).Promise;
 var _PromiseTestUtils = Components.utils.import("resource://testing-common/PromiseTestUtils.jsm", {}).PromiseTestUtils;
 var _Task = Components.utils.import("resource://gre/modules/Task.jsm", {}).Task;
-
-let _NetUtil = Components.utils.import("resource://gre/modules/NetUtil.jsm", {}).NetUtil;
-
 Components.utils.importGlobalProperties(["XMLHttpRequest"]);
 
 // Support a common assertion library, Assert.jsm.
 var AssertCls = Components.utils.import("resource://testing-common/Assert.jsm", null).Assert;
 // Pass a custom report function for xpcshell-test style reporting.
 var Assert = new AssertCls(function(err, message, stack) {
   if (err) {
     do_report_result(false, err.message, err.stack);
@@ -1563,18 +1560,19 @@ try {
 
 function _load_mozinfo() {
   let mozinfoFile = Components.classes["@mozilla.org/file/local;1"]
     .createInstance(Components.interfaces.nsIFile);
   mozinfoFile.initWithPath(_MOZINFO_JS_PATH);
   let stream = Components.classes["@mozilla.org/network/file-input-stream;1"]
     .createInstance(Components.interfaces.nsIFileInputStream);
   stream.init(mozinfoFile, -1, 0, 0);
-  let bytes = _NetUtil.readInputStream(stream, stream.available());
-  let mozinfo = JSON.parse((new TextDecoder()).decode(bytes));
+  let json = Components.classes["@mozilla.org/dom/json;1"]
+    .createInstance(Components.interfaces.nsIJSON);
+  let mozinfo = json.decodeFromStream(stream, stream.available());
   stream.close();
   return mozinfo;
 }
 
 Object.defineProperty(this, "mozinfo", {
   configurable: true,
   get() {
     let _mozinfo = _load_mozinfo();
--- a/toolkit/components/contextualidentity/ContextualIdentityService.jsm
+++ b/toolkit/components/contextualidentity/ContextualIdentityService.jsm
@@ -28,18 +28,16 @@ XPCOMUtils.defineLazyGetter(this, "gText
 XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
                                   "resource://gre/modules/AsyncShutdown.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
                                   "resource://gre/modules/DeferredTask.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
-                                  "resource://gre/modules/NetUtil.jsm");
 
 function _TabRemovalObserver(resolver, tabParentIds) {
   this._resolver = resolver;
   this._tabParentIds = tabParentIds;
   Services.obs.addObserver(this, "ipc:browser-destroyed");
 }
 
 _TabRemovalObserver.prototype = {
@@ -312,18 +310,19 @@ function _ContextualIdentityService(path
 
     try {
       // This reads the file and automatically detects the UTF-8 encoding.
       let inputStream = Cc["@mozilla.org/network/file-input-stream;1"]
                           .createInstance(Ci.nsIFileInputStream);
       inputStream.init(new FileUtils.File(this._path),
                        FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
       try {
-        let bytes = NetUtil.readInputStreamToString(inputStream, inputStream.available());
-        let data = JSON.parse(gTextDecoder.decode(bytes));
+        let json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
+        let data = json.decodeFromStream(inputStream,
+                                         inputStream.available());
         this._identities = data.identities;
         this._lastUserContextId = data.lastUserContextId;
 
         this._dataReady = true;
       } finally {
         inputStream.close();
       }
     } catch (error) {
--- a/toolkit/components/search/nsSearchService.js
+++ b/toolkit/components/search/nsSearchService.js
@@ -18,17 +18,16 @@ XPCOMUtils.defineLazyModuleGetters(this,
   DeferredTask: "resource://gre/modules/DeferredTask.jsm",
   OS: "resource://gre/modules/osfile.jsm",
   TelemetryStopwatch: "resource://gre/modules/TelemetryStopwatch.jsm",
   Deprecated: "resource://gre/modules/Deprecated.jsm",
   SearchStaticData: "resource://gre/modules/SearchStaticData.jsm",
   setTimeout: "resource://gre/modules/Timer.jsm",
   clearTimeout: "resource://gre/modules/Timer.jsm",
   Lz4: "resource://gre/modules/lz4.js",
-  NetUtil: "resource://gre/modules/NetUtil.jsm",
 });
 
 XPCOMUtils.defineLazyServiceGetters(this, {
   gTextToSubURI: ["@mozilla.org/intl/texttosuburi;1", "nsITextToSubURI"],
   gEnvironment: ["@mozilla.org/process/environment;1", "nsIEnvironment"],
   gChromeReg: ["@mozilla.org/chrome/chrome-registry;1", "nsIChromeRegistry"],
 });
 
@@ -42,17 +41,16 @@ const BinaryInputStream = Components.Con
 Cu.importGlobalProperties(["XMLHttpRequest"]);
 
 // A text encoder to UTF8, used whenever we commit the cache to disk.
 XPCOMUtils.defineLazyGetter(this, "gEncoder",
                             function() {
                               return new TextEncoder();
                             });
 
-
 const MODE_RDONLY   = 0x01;
 const MODE_WRONLY   = 0x02;
 const MODE_CREATE   = 0x08;
 const MODE_APPEND   = 0x10;
 const MODE_TRUNCATE = 0x20;
 const PERMS_FILE    = 0o644;
 
 // Directory service keys
@@ -950,18 +948,19 @@ var gInitialized = false;
 function notifyAction(aEngine, aVerb) {
   if (gInitialized) {
     LOG("NOTIFY: Engine: \"" + aEngine.name + "\"; Verb: \"" + aVerb + "\"");
     Services.obs.notifyObservers(aEngine, SEARCH_ENGINE_TOPIC, aVerb);
   }
 }
 
 function parseJsonFromStream(aInputStream) {
-  let bytes = NetUtil.readInputStream(aInputStream, aInputStream.available());
-  return JSON.parse(new TextDecoder().decode(bytes));
+  const json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
+  const data = json.decodeFromStream(aInputStream, aInputStream.available());
+  return data;
 }
 
 /**
  * Simple object representing a name/value pair.
  */
 function QueryParameter(aName, aValue, aPurpose) {
   if (!aName || (aValue == null))
     FAIL("missing name or value for QueryParameter!");
--- a/toolkit/components/search/tests/xpcshell/head_search.js
+++ b/toolkit/components/search/tests/xpcshell/head_search.js
@@ -7,18 +7,16 @@ Cu.import("resource://gre/modules/FileUt
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://testing-common/AppInfo.jsm");
 Cu.import("resource://testing-common/httpd.js");
 XPCOMUtils.defineLazyModuleGetter(this, "TestUtils",
                                   "resource://testing-common/TestUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
-                                  "resource://gre/modules/NetUtil.jsm");
 
 const BROWSER_SEARCH_PREF = "browser.search.";
 const NS_APP_SEARCH_DIR = "SrchPlugns";
 
 const MODE_RDONLY = FileUtils.MODE_RDONLY;
 const MODE_WRONLY = FileUtils.MODE_WRONLY;
 const MODE_CREATE = FileUtils.MODE_CREATE;
 const MODE_TRUNCATE = FileUtils.MODE_TRUNCATE;
@@ -275,18 +273,19 @@ function getDefaultEngineName(isUS) {
  * Waits for the cache file to be saved.
  * @return {Promise} Resolved when the cache file is saved.
  */
 function promiseAfterCache() {
   return waitForSearchNotification("write-cache-to-disk-complete");
 }
 
 function parseJsonFromStream(aInputStream) {
-  let bytes = NetUtil.readInputStream(aInputStream, aInputStream.available());
-  return JSON.parse((new TextDecoder()).decode(bytes));
+  const json = Cc["@mozilla.org/dom/json;1"].createInstance(Components.interfaces.nsIJSON);
+  const data = json.decodeFromStream(aInputStream, aInputStream.available());
+  return data;
 }
 
 /**
  * Read a JSON file and return the JS object
  */
 function readJSONFile(aFile) {
   let stream = Cc["@mozilla.org/network/file-input-stream;1"].
                createInstance(Ci.nsIFileInputStream);
--- a/toolkit/components/telemetry/tests/unit/head.js
+++ b/toolkit/components/telemetry/tests/unit/head.js
@@ -15,18 +15,16 @@ Cu.import("resource://gre/modules/AppCon
 XPCOMUtils.defineLazyModuleGetter(this, "AddonTestUtils",
                                   "resource://testing-common/AddonTestUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetrySend",
                                   "resource://gre/modules/TelemetrySend.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Log",
                                   "resource://gre/modules/Log.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
-                                  "resource://gre/modules/NetUtil.jsm");
 
 const gIsWindows = AppConstants.platform == "win";
 const gIsMac = AppConstants.platform == "macosx";
 const gIsAndroid = AppConstants.platform == "android";
 const gIsLinux = AppConstants.platform == "linux";
 
 const Telemetry = Services.telemetry;
 
@@ -126,16 +124,17 @@ const PingServer = {
 /**
  * Decode the payload of an HTTP request into a ping.
  * @param {Object} request The data representing an HTTP request (nsIHttpRequest).
  * @return {Object} The decoded ping payload.
  */
 function decodeRequestPayload(request) {
   let s = request.bodyInputStream;
   let payload = null;
+  let decoder = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
 
   if (request.hasHeader("content-encoding") &&
       request.getHeader("content-encoding") == "gzip") {
     let observer = {
       buffer: "",
       onStreamComplete(loader, context, status, length, result) {
         this.buffer = String.fromCharCode.apply(this, result);
       }
@@ -153,18 +152,17 @@ function decodeRequestPayload(request) {
     converter.onStopRequest(null, null, null);
     let unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                     .createInstance(Ci.nsIScriptableUnicodeConverter);
     unicodeConverter.charset = "UTF-8";
     let utf8string = unicodeConverter.ConvertToUnicode(observer.buffer);
     utf8string += unicodeConverter.Finish();
     payload = JSON.parse(utf8string);
   } else {
-    let bytes = NetUtil.readInputStream(s, s.available());
-    payload = JSON.parse((new TextDecoder()).decode(bytes));
+    payload = decoder.decodeFromStream(s, s.available());
   }
 
   return payload;
 }
 
 function wrapWithExceptionHandler(f) {
   function wrapper(...args) {
     try {
--- a/toolkit/modules/JSONFile.jsm
+++ b/toolkit/modules/JSONFile.jsm
@@ -41,19 +41,16 @@ Cu.import("resource://gre/modules/XPCOMU
 XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
                                   "resource://gre/modules/AsyncShutdown.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
                                   "resource://gre/modules/DeferredTask.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
-                                  "resource://gre/modules/NetUtil.jsm");
-
 
 XPCOMUtils.defineLazyGetter(this, "gTextDecoder", function() {
   return new TextDecoder();
 });
 
 XPCOMUtils.defineLazyGetter(this, "gTextEncoder", function() {
   return new TextEncoder();
 });
@@ -239,18 +236,18 @@ JSONFile.prototype = {
     let data = {};
 
     try {
       // This reads the file and automatically detects the UTF-8 encoding.
       let inputStream = new FileInputStream(new FileUtils.File(this.path),
                                             FileUtils.MODE_RDONLY,
                                             FileUtils.PERMS_FILE, 0);
       try {
-        let bytes = NetUtil.readInputStream(inputStream, inputStream.available());
-        data = JSON.parse(gTextDecoder.decode(bytes));
+        let json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
+        data = json.decodeFromStream(inputStream, inputStream.available());
       } finally {
         inputStream.close();
       }
     } catch (ex) {
       // If an exception occurred because the file did not exist, we should just
       // start with new data.  Other errors may indicate that the file is
       // corrupt, thus we move it to a backup location before allowing it to be
       // overwritten by an empty file.
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -52,20 +52,16 @@ XPCOMUtils.defineLazyServiceGetters(this
   Blocklist: ["@mozilla.org/extensions/blocklist;1", "nsIBlocklistService"],
   ChromeRegistry: ["@mozilla.org/chrome/chrome-registry;1", "nsIChromeRegistry"],
   ResProtocolHandler: ["@mozilla.org/network/protocol;1?name=resource", "nsIResProtocolHandler"],
   AddonPolicyService: ["@mozilla.org/addons/policy-service;1", "nsIAddonPolicyService"],
   AddonPathService: ["@mozilla.org/addon-path-service;1", "amIAddonPathService"],
   aomStartup: ["@mozilla.org/addons/addon-manager-startup;1", "amIAddonManagerStartup"],
 });
 
-XPCOMUtils.defineLazyGetter(this, "gTextDecoder", () => {
-  return new TextDecoder();
-});
-
 Cu.importGlobalProperties(["URL"]);
 
 const nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile",
                                        "initWithPath");
 
 const PREF_DB_SCHEMA                  = "extensions.databaseSchema";
 const PREF_XPI_STATE                  = "extensions.xpiState";
 const PREF_BLOCKLIST_ITEM_URL         = "extensions.blocklist.itemURL";
@@ -2914,21 +2910,22 @@ this.XPIProvider = {
         }
 
         // Check for a cached metadata for this add-on, it may contain updated
         // compatibility information
         if (!foreignInstall) {
           logger.debug("Found updated metadata for " + id + " in " + location.name);
           let fis = Cc["@mozilla.org/network/file-input-stream;1"].
                        createInstance(Ci.nsIFileInputStream);
+          let json = Cc["@mozilla.org/dom/json;1"].
+                     createInstance(Ci.nsIJSON);
+
           try {
             fis.init(jsonfile, -1, 0, 0);
-
-            let bytes = NetUtil.readInputStream(fis, jsonfile.fileSize);
-            let metadata = JSON.parse(gTextDecoder.decode(bytes));
+            let metadata = json.decodeFromStream(fis, jsonfile.fileSize);
             addon.importMetadata(metadata);
 
             // Pass this through to addMetadata so it knows this add-on was
             // likely installed through the UI
             aManifests[location.name][id] = addon;
           } catch (e) {
             // If some data can't be recovered from the cached metadata then it
             // is unlikely to be a problem big enough to justify throwing away
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js
@@ -1,16 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // Tests that a pending upgrade during a schema update doesn't break things
 
-Components.utils.importGlobalProperties(["File"]);
-
 var addon1 = {
   id: "addon1@tests.mozilla.org",
   version: "2.0",
   name: "Test 1",
   targetApplications: [{
     id: "xpcshell@tests.mozilla.org",
     minVersion: "1",
     maxVersion: "1"
@@ -115,26 +113,23 @@ async function run_test_1() {
 
       let jsonfile = gProfD.clone();
       jsonfile.append("extensions");
       jsonfile.append("staged");
       jsonfile.append("addon3@tests.mozilla.org.json");
       do_check_true(jsonfile.exists());
 
       // Remove an unnecessary property from the cached manifest
-      let file = await File.createFromNsIFile(jsonfile);
-
-      let addonObj = await new Promise(resolve => {
-        let fr = new FileReader();
-        fr.readAsText(file);
-        fr.onloadend = () => {
-          resolve(JSON.parse(fr.result));
-        }
-      });
-
+      let fis = AM_Cc["@mozilla.org/network/file-input-stream;1"].
+                   createInstance(AM_Ci.nsIFileInputStream);
+      let json = AM_Cc["@mozilla.org/dom/json;1"].
+                 createInstance(AM_Ci.nsIJSON);
+      fis.init(jsonfile, -1, 0, 0);
+      let addonObj = json.decodeFromStream(fis, jsonfile.fileSize);
+      fis.close();
       delete addonObj.optionsType;
 
       let stream = AM_Cc["@mozilla.org/network/file-output-stream;1"].
                    createInstance(AM_Ci.nsIFileOutputStream);
       let converter = AM_Cc["@mozilla.org/intl/converter-output-stream;1"].
                       createInstance(AM_Ci.nsIConverterOutputStream);
       stream.init(jsonfile, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE |
                             FileUtils.MODE_TRUNCATE, FileUtils.PERMS_FILE,
@@ -259,26 +254,23 @@ async function run_test_2() {
 
       let jsonfile = gProfD.clone();
       jsonfile.append("extensions");
       jsonfile.append("staged");
       jsonfile.append("addon3@tests.mozilla.org.json");
       do_check_true(jsonfile.exists());
 
       // Remove an unnecessary property from the cached manifest
-      let file = await File.createFromNsIFile(jsonfile);
-
-      let addonObj = await new Promise(resolve => {
-        let fr = new FileReader();
-        fr.readAsText(file);
-        fr.onloadend = () => {
-          resolve(JSON.parse(fr.result));
-        }
-      });
-
+      let fis = AM_Cc["@mozilla.org/network/file-input-stream;1"].
+                   createInstance(AM_Ci.nsIFileInputStream);
+      let json = AM_Cc["@mozilla.org/dom/json;1"].
+                 createInstance(AM_Ci.nsIJSON);
+      fis.init(jsonfile, -1, 0, 0);
+      let addonObj = json.decodeFromStream(fis, jsonfile.fileSize);
+      fis.close();
       delete addonObj.optionsType;
 
       let stream = AM_Cc["@mozilla.org/network/file-output-stream;1"].
                    createInstance(AM_Ci.nsIFileOutputStream);
       let converter = AM_Cc["@mozilla.org/intl/converter-output-stream;1"].
                       createInstance(AM_Ci.nsIConverterOutputStream);
       stream.init(jsonfile, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE |
                             FileUtils.MODE_TRUNCATE, FileUtils.PERMS_FILE,