Bug 1206166 - Move FetchUtil::Consume methods into separate BodyUtil class and update Fetch.cpp and ServiceWorkerEvents.cpp accordingly. r=kitcambridge
authorHuma Zafar <zafar.huma@gmail.com>
Sat, 27 Feb 2016 13:54:11 -0800
changeset 324193 faaf448b9788574b99b0fd0dc554879899edcdf4
parent 324192 aa740a3f7dc97a1f956eae37e3065a89fccef60d
child 324194 f505516f9239684f41444c2bc5f3fd07e7d76c0a
push id1128
push userjlund@mozilla.com
push dateWed, 01 Jun 2016 01:31:59 +0000
treeherdermozilla-release@fe0d30de989d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskitcambridge
bugs1206166
milestone47.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1206166 - Move FetchUtil::Consume methods into separate BodyUtil class and update Fetch.cpp and ServiceWorkerEvents.cpp accordingly. r=kitcambridge MozReview-Commit-ID: KgEccQ2LPpC
dom/base/BodyUtil.cpp
dom/base/BodyUtil.h
dom/base/moz.build
dom/fetch/Fetch.cpp
dom/fetch/FetchUtil.cpp
dom/fetch/FetchUtil.h
dom/push/PushNotifier.cpp
dom/workers/ServiceWorkerEvents.cpp
new file mode 100644
--- /dev/null
+++ b/dom/base/BodyUtil.cpp
@@ -0,0 +1,574 @@
+/* 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 "BodyUtil.h"
+
+#include "nsError.h"
+#include "nsString.h"
+#include "nsIGlobalObject.h"
+#include "nsIUnicodeDecoder.h"
+
+#include "nsCharSeparatedTokenizer.h"
+#include "nsDOMString.h"
+#include "nsNetUtil.h"
+#include "nsReadableUtils.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/EncodingUtils.h"
+#include "mozilla/dom/Exceptions.h"
+#include "mozilla/dom/FetchUtil.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FormData.h"
+#include "mozilla/dom/Headers.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/URLSearchParams.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+class StreamDecoder final
+{
+  nsCOMPtr<nsIUnicodeDecoder> mDecoder;
+  nsString mDecoded;
+
+public:
+  StreamDecoder()
+    : mDecoder(EncodingUtils::DecoderForEncoding("UTF-8"))
+  {
+    MOZ_ASSERT(mDecoder);
+  }
+
+  nsresult
+  AppendText(const char* aSrcBuffer, uint32_t aSrcBufferLen)
+  {
+    int32_t destBufferLen;
+    nsresult rv =
+      mDecoder->GetMaxLength(aSrcBuffer, aSrcBufferLen, &destBufferLen);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (!mDecoded.SetCapacity(mDecoded.Length() + destBufferLen, fallible)) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    char16_t* destBuffer = mDecoded.BeginWriting() + mDecoded.Length();
+    int32_t totalChars = mDecoded.Length();
+
+    int32_t srcLen = (int32_t) aSrcBufferLen;
+    int32_t outLen = destBufferLen;
+    rv = mDecoder->Convert(aSrcBuffer, &srcLen, destBuffer, &outLen);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+    totalChars += outLen;
+    mDecoded.SetLength(totalChars);
+
+    return NS_OK;
+  }
+
+  nsString&
+  GetText()
+  {
+    return mDecoded;
+  }
+};
+
+// Reads over a CRLF and positions start after it.
+static bool
+PushOverLine(nsACString::const_iterator& aStart)
+{
+  if (*aStart == nsCRT::CR && (aStart.size_forward() > 1) && *(++aStart) == nsCRT::LF) {
+    ++aStart; // advance to after CRLF
+    return true;
+  }
+
+  return false;
+}
+
+class MOZ_STACK_CLASS FillFormIterator final
+  : public URLSearchParams::ForEachIterator
+{
+public:
+  explicit FillFormIterator(FormData* aFormData)
+    : mFormData(aFormData)
+  {
+    MOZ_ASSERT(aFormData);
+  }
+
+  bool URLParamsIterator(const nsString& aName,
+                         const nsString& aValue) override
+  {
+    ErrorResult rv;
+    mFormData->Append(aName, aValue, rv);
+    MOZ_ASSERT(!rv.Failed());
+    return true;
+  }
+
+private:
+  FormData* mFormData;
+};
+
+/**
+ * A simple multipart/form-data parser as defined in RFC 2388 and RFC 2046.
+ * This does not respect any encoding specified per entry, using UTF-8
+ * throughout. This is as the Fetch spec states in the consume body algorithm.
+ * Borrows some things from Necko's nsMultiMixedConv, but is simpler since
+ * unlike Necko we do not have to deal with receiving incomplete chunks of data.
+ *
+ * This parser will fail the entire parse on any invalid entry, so it will
+ * never return a partially filled FormData.
+ * The content-disposition header is used to figure out the name and filename
+ * entries. The inclusion of the filename parameter decides if the entry is
+ * inserted into the FormData as a string or a File.
+ *
+ * File blobs are copies of the underlying data string since we cannot adopt
+ * char* chunks embedded within the larger body without significant effort.
+ * FIXME(nsm): Bug 1127552 - We should add telemetry to calls to formData() and
+ * friends to figure out if Fetch ends up copying big blobs to see if this is
+ * worth optimizing.
+ */
+class MOZ_STACK_CLASS FormDataParser
+{
+private:
+  RefPtr<FormData> mFormData;
+  nsCString mMimeType;
+  nsCString mData;
+
+  // Entry state, reset in START_PART.
+  nsCString mName;
+  nsCString mFilename;
+  nsCString mContentType;
+
+  enum
+  {
+    START_PART,
+    PARSE_HEADER,
+    PARSE_BODY,
+  } mState;
+
+  nsIGlobalObject* mParentObject;
+
+  // Reads over a boundary and sets start to the position after the end of the
+  // boundary. Returns false if no boundary is found immediately.
+  bool
+  PushOverBoundary(const nsACString& aBoundaryString,
+                   nsACString::const_iterator& aStart,
+                   nsACString::const_iterator& aEnd)
+  {
+    // We copy the end iterator to keep the original pointing to the real end
+    // of the string.
+    nsACString::const_iterator end(aEnd);
+    const char* beginning = aStart.get();
+    if (FindInReadable(aBoundaryString, aStart, end)) {
+      // We either should find the body immediately, or after 2 chars with the
+      // 2 chars being '-', everything else is failure.
+      if ((aStart.get() - beginning) == 0) {
+        aStart.advance(aBoundaryString.Length());
+        return true;
+      }
+
+      if ((aStart.get() - beginning) == 2) {
+        if (*(--aStart) == '-' && *(--aStart) == '-') {
+          aStart.advance(aBoundaryString.Length() + 2);
+          return true;
+        }
+      }
+    }
+
+    return false;
+  }
+
+  bool
+  ParseHeader(nsACString::const_iterator& aStart,
+              nsACString::const_iterator& aEnd,
+              bool* aWasEmptyHeader)
+  {
+    nsAutoCString headerName, headerValue;
+    if (!FetchUtil::ExtractHeader(aStart, aEnd,
+                                  headerName, headerValue,
+                                  aWasEmptyHeader)) {
+      return false;
+    }
+    if (*aWasEmptyHeader) {
+      return true;
+    }
+
+    if (headerName.LowerCaseEqualsLiteral("content-disposition")) {
+      nsCCharSeparatedTokenizer tokenizer(headerValue, ';');
+      bool seenFormData = false;
+      while (tokenizer.hasMoreTokens()) {
+        const nsDependentCSubstring& token = tokenizer.nextToken();
+        if (token.IsEmpty()) {
+          continue;
+        }
+
+        if (token.EqualsLiteral("form-data")) {
+          seenFormData = true;
+          continue;
+        }
+
+        if (seenFormData &&
+            StringBeginsWith(token, NS_LITERAL_CSTRING("name="))) {
+          mName = StringTail(token, token.Length() - 5);
+          mName.Trim(" \"");
+          continue;
+        }
+
+        if (seenFormData &&
+            StringBeginsWith(token, NS_LITERAL_CSTRING("filename="))) {
+          mFilename = StringTail(token, token.Length() - 9);
+          mFilename.Trim(" \"");
+          continue;
+        }
+      }
+
+      if (mName.IsVoid()) {
+        // Could not parse a valid entry name.
+        return false;
+      }
+    } else if (headerName.LowerCaseEqualsLiteral("content-type")) {
+      mContentType = headerValue;
+    }
+
+    return true;
+  }
+
+  // The end of a body is marked by a CRLF followed by the boundary. So the
+  // CRLF is part of the boundary and not the body, but any prior CRLFs are
+  // part of the body. This will position the iterator at the beginning of the
+  // boundary (after the CRLF).
+  bool
+  ParseBody(const nsACString& aBoundaryString,
+            nsACString::const_iterator& aStart,
+            nsACString::const_iterator& aEnd)
+  {
+    const char* beginning = aStart.get();
+
+    // Find the boundary marking the end of the body.
+    nsACString::const_iterator end(aEnd);
+    if (!FindInReadable(aBoundaryString, aStart, end)) {
+      return false;
+    }
+
+    // We found a boundary, strip the just prior CRLF, and consider
+    // everything else the body section.
+    if (aStart.get() - beginning < 2) {
+      // Only the first entry can have a boundary right at the beginning. Even
+      // an empty body will have a CRLF before the boundary. So this is
+      // a failure.
+      return false;
+    }
+
+    // Check that there is a CRLF right before the boundary.
+    aStart.advance(-2);
+
+    // Skip optional hyphens.
+    if (*aStart == '-' && *(aStart.get()+1) == '-') {
+      if (aStart.get() - beginning < 2) {
+        return false;
+      }
+
+      aStart.advance(-2);
+    }
+
+    if (*aStart != nsCRT::CR || *(aStart.get()+1) != nsCRT::LF) {
+      return false;
+    }
+
+    nsAutoCString body(beginning, aStart.get() - beginning);
+
+    // Restore iterator to after the \r\n as we promised.
+    // We do not need to handle the extra hyphens case since our boundary
+    // parser in PushOverBoundary()
+    aStart.advance(2);
+
+    if (!mFormData) {
+      mFormData = new FormData();
+    }
+
+    NS_ConvertUTF8toUTF16 name(mName);
+
+    if (mFilename.IsVoid()) {
+      ErrorResult rv;
+      mFormData->Append(name, NS_ConvertUTF8toUTF16(body), rv);
+      MOZ_ASSERT(!rv.Failed());
+    } else {
+      // Unfortunately we've to copy the data first since all our strings are
+      // going to free it. We also need fallible alloc, so we can't just use
+      // ToNewCString().
+      char* copy = static_cast<char*>(moz_xmalloc(body.Length()));
+      if (!copy) {
+        NS_WARNING("Failed to copy File entry body.");
+        return false;
+      }
+      nsCString::const_iterator bodyIter, bodyEnd;
+      body.BeginReading(bodyIter);
+      body.EndReading(bodyEnd);
+      char *p = copy;
+      while (bodyIter != bodyEnd) {
+        *p++ = *bodyIter++;
+      }
+      p = nullptr;
+
+      RefPtr<Blob> file =
+        File::CreateMemoryFile(mParentObject,
+                               reinterpret_cast<void *>(copy), body.Length(),
+                               NS_ConvertUTF8toUTF16(mFilename),
+                               NS_ConvertUTF8toUTF16(mContentType), /* aLastModifiedDate */ 0);
+      Optional<nsAString> dummy;
+      ErrorResult rv;
+      mFormData->Append(name, *file, dummy, rv);
+      if (NS_WARN_IF(rv.Failed())) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+public:
+  FormDataParser(const nsACString& aMimeType, const nsACString& aData, nsIGlobalObject* aParent)
+    : mMimeType(aMimeType), mData(aData), mState(START_PART), mParentObject(aParent)
+  {
+  }
+
+  bool
+  Parse()
+  {
+    // Determine boundary from mimetype.
+    const char* boundaryId = nullptr;
+    boundaryId = strstr(mMimeType.BeginWriting(), "boundary");
+    if (!boundaryId) {
+      return false;
+    }
+
+    boundaryId = strchr(boundaryId, '=');
+    if (!boundaryId) {
+      return false;
+    }
+
+    // Skip over '='.
+    boundaryId++;
+
+    char *attrib = (char *) strchr(boundaryId, ';');
+    if (attrib) *attrib = '\0';
+
+    nsAutoCString boundaryString(boundaryId);
+    if (attrib) *attrib = ';';
+
+    boundaryString.Trim(" \"");
+
+    if (boundaryString.Length() == 0) {
+      return false;
+    }
+
+    nsACString::const_iterator start, end;
+    mData.BeginReading(start);
+    // This should ALWAYS point to the end of data.
+    // Helpers make copies.
+    mData.EndReading(end);
+
+    while (start != end) {
+      switch(mState) {
+        case START_PART:
+          mName.SetIsVoid(true);
+          mFilename.SetIsVoid(true);
+          mContentType = NS_LITERAL_CSTRING("text/plain");
+
+          // MUST start with boundary.
+          if (!PushOverBoundary(boundaryString, start, end)) {
+            return false;
+          }
+
+          if (start != end && *start == '-') {
+            // End of data.
+            if (!mFormData) {
+              mFormData = new FormData();
+            }
+            return true;
+          }
+
+          if (!PushOverLine(start)) {
+            return false;
+          }
+          mState = PARSE_HEADER;
+          break;
+
+        case PARSE_HEADER:
+          bool emptyHeader;
+          if (!ParseHeader(start, end, &emptyHeader)) {
+            return false;
+          }
+
+          if (emptyHeader && !PushOverLine(start)) {
+            return false;
+          }
+
+          mState = emptyHeader ? PARSE_BODY : PARSE_HEADER;
+          break;
+
+        case PARSE_BODY:
+          if (mName.IsVoid()) {
+            NS_WARNING("No content-disposition header with a valid name was "
+                       "found. Failing at body parse.");
+            return false;
+          }
+
+          if (!ParseBody(boundaryString, start, end)) {
+            return false;
+          }
+
+          mState = START_PART;
+          break;
+
+        default:
+          MOZ_CRASH("Invalid case");
+      }
+    }
+
+    NS_NOTREACHED("Should never reach here.");
+    return false;
+  }
+
+  already_AddRefed<FormData> GetFormData()
+  {
+    return mFormData.forget();
+  }
+};
+}
+
+// static
+void
+BodyUtil::ConsumeArrayBuffer(JSContext* aCx,
+                              JS::MutableHandle<JSObject*> aValue,
+                              uint32_t aInputLength, uint8_t* aInput,
+                              ErrorResult& aRv)
+{
+  JS::Rooted<JSObject*> arrayBuffer(aCx);
+  arrayBuffer = JS_NewArrayBufferWithContents(aCx, aInputLength,
+    reinterpret_cast<void *>(aInput));
+  if (!arrayBuffer) {
+    JS_ClearPendingException(aCx);
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return;
+  }
+  aValue.set(arrayBuffer);
+}
+
+// static
+already_AddRefed<Blob>
+BodyUtil::ConsumeBlob(nsISupports* aParent, const nsString& aMimeType,
+                       uint32_t aInputLength, uint8_t* aInput,
+                       ErrorResult& aRv)
+{
+  RefPtr<Blob> blob =
+    Blob::CreateMemoryBlob(aParent,
+                           reinterpret_cast<void *>(aInput), aInputLength,
+                           aMimeType);
+
+  if (!blob) {
+    aRv.Throw(NS_ERROR_DOM_UNKNOWN_ERR);
+    return nullptr;
+  }
+  return blob.forget();
+}
+
+// static
+already_AddRefed<FormData>
+BodyUtil::ConsumeFormData(nsIGlobalObject* aParent, const nsCString& aMimeType,
+                           const nsCString& aStr, ErrorResult& aRv)
+{
+  NS_NAMED_LITERAL_CSTRING(formDataMimeType, "multipart/form-data");
+
+  // Allow semicolon separated boundary/encoding suffix like multipart/form-data; boundary=
+  // but disallow multipart/form-datafoobar.
+  bool isValidFormDataMimeType = StringBeginsWith(aMimeType, formDataMimeType);
+
+  if (isValidFormDataMimeType && aMimeType.Length() > formDataMimeType.Length()) {
+    isValidFormDataMimeType = aMimeType[formDataMimeType.Length()] == ';';
+  }
+
+  if (isValidFormDataMimeType) {
+    FormDataParser parser(aMimeType, aStr, aParent);
+    if (!parser.Parse()) {
+      aRv.ThrowTypeError<MSG_BAD_FORMDATA>();
+      return nullptr;
+    }
+
+    RefPtr<FormData> fd = parser.GetFormData();
+    MOZ_ASSERT(fd);
+    return fd.forget();
+  }
+
+  NS_NAMED_LITERAL_CSTRING(urlDataMimeType, "application/x-www-form-urlencoded");
+  bool isValidUrlEncodedMimeType = StringBeginsWith(aMimeType, urlDataMimeType);
+
+  if (isValidUrlEncodedMimeType && aMimeType.Length() > urlDataMimeType.Length()) {
+    isValidUrlEncodedMimeType = aMimeType[urlDataMimeType.Length()] == ';';
+  }
+
+  if (isValidUrlEncodedMimeType) {
+    URLParams params;
+    params.ParseInput(aStr);
+
+    RefPtr<FormData> fd = new FormData(aParent);
+    FillFormIterator iterator(fd);
+    DebugOnly<bool> status = params.ForEach(iterator);
+    MOZ_ASSERT(status);
+
+    return fd.forget();
+  }
+
+  aRv.ThrowTypeError<MSG_BAD_FORMDATA>();
+  return nullptr;
+}
+
+// static
+nsresult
+BodyUtil::ConsumeText(uint32_t aInputLength, uint8_t* aInput,
+                       nsString& aText)
+{
+  StreamDecoder decoder;
+  nsresult rv = decoder.AppendText(reinterpret_cast<char*>(aInput),
+                                   aInputLength);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  aText = decoder.GetText();
+  return NS_OK;
+}
+
+// static
+void
+BodyUtil::ConsumeJson(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
+                       const nsString& aStr, ErrorResult& aRv)
+{
+  aRv.MightThrowJSException();
+
+  AutoForceSetExceptionOnContext forceExn(aCx);
+  JS::Rooted<JS::Value> json(aCx);
+  if (!JS_ParseJSON(aCx, aStr.get(), aStr.Length(), &json)) {
+    if (!JS_IsExceptionPending(aCx)) {
+      aRv.Throw(NS_ERROR_DOM_UNKNOWN_ERR);
+      return;
+    }
+
+    JS::Rooted<JS::Value> exn(aCx);
+    DebugOnly<bool> gotException = JS_GetPendingException(aCx, &exn);
+    MOZ_ASSERT(gotException);
+
+    JS_ClearPendingException(aCx);
+    aRv.ThrowJSException(aCx, exn);
+    return;
+  }
+
+  aValue.set(json);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/base/BodyUtil.h
@@ -0,0 +1,68 @@
+/* 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 mozilla_dom_BodyUtil_h
+#define mozilla_dom_BodyUtil_h
+
+#include "nsString.h"
+#include "nsError.h"
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FormData.h"
+
+namespace mozilla {
+namespace dom {
+
+class BodyUtil final
+{
+private:
+  BodyUtil() = delete;
+
+public:
+  /**
+   * Creates an array buffer from an array, assigning the result to |aValue|.
+   * The array buffer takes ownership of |aInput|, which must be allocated
+   * by |malloc|.
+   */
+  static void
+  ConsumeArrayBuffer(JSContext* aCx, JS::MutableHandle<JSObject*> aValue,
+                     uint32_t aInputLength, uint8_t* aInput, ErrorResult& aRv);
+
+  /**
+   * Creates an in-memory blob from an array. The blob takes ownership of
+   * |aInput|, which must be allocated by |malloc|.
+   */
+  static already_AddRefed<Blob>
+  ConsumeBlob(nsISupports* aParent, const nsString& aMimeType,
+              uint32_t aInputLength, uint8_t* aInput, ErrorResult& aRv);
+
+  /**
+   * Creates a form data object from a UTF-8 encoded |aStr|. Returns |nullptr|
+   * and sets |aRv| to MSG_BAD_FORMDATA if |aStr| contains invalid data.
+   */
+  static already_AddRefed<FormData>
+  ConsumeFormData(nsIGlobalObject* aParent, const nsCString& aMimeType,
+                  const nsCString& aStr, ErrorResult& aRv);
+
+  /**
+   * UTF-8 decodes |aInput| into |aText|. The caller may free |aInput|
+   * once this method returns.
+   */
+  static nsresult
+  ConsumeText(uint32_t aInputLength, uint8_t* aInput, nsString& aText);
+
+  /**
+   * Parses a UTF-8 encoded |aStr| as JSON, assigning the result to |aValue|.
+   * Sets |aRv| to a syntax error if |aStr| contains invalid data.
+   */
+  static void
+  ConsumeJson(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
+              const nsString& aStr, ErrorResult& aRv);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_BodyUtil_h
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -151,16 +151,17 @@ EXPORTS.mozilla += [
 ]
 
 EXPORTS.mozilla.dom += [
     '!UseCounterList.h',
     'AnonymousContent.h',
     'Attr.h',
     'BarProps.h',
     'BlobSet.h',
+    'BodyUtil.h',
     'ChildIterator.h',
     'ChromeNodeList.h',
     'ChromeUtils.h',
     'Comment.h',
     'Console.h',
     'DirectionalityUtils.h',
     'DocumentFragment.h',
     'DocumentType.h',
@@ -218,16 +219,17 @@ EXPORTS.mozilla.dom += [
     'WebSocket.h',
     'WindowOrientationObserver.h',
 ]
 
 UNIFIED_SOURCES += [
     'AnonymousContent.cpp',
     'Attr.cpp',
     'BarProps.cpp',
+    'BodyUtil.cpp',
     'ChildIterator.cpp',
     'ChromeNodeList.cpp',
     'ChromeUtils.cpp',
     'Comment.cpp',
     'Console.cpp',
     'ConsoleReportCollector.cpp',
     'Crypto.cpp',
     'DirectionalityUtils.cpp',
--- a/dom/fetch/Fetch.cpp
+++ b/dom/fetch/Fetch.cpp
@@ -16,16 +16,17 @@
 #include "nsCharSeparatedTokenizer.h"
 #include "nsDOMString.h"
 #include "nsNetUtil.h"
 #include "nsReadableUtils.h"
 #include "nsStreamUtils.h"
 #include "nsStringStream.h"
 
 #include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BodyUtil.h"
 #include "mozilla/dom/EncodingUtils.h"
 #include "mozilla/dom/Exceptions.h"
 #include "mozilla/dom/FetchDriver.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/FormData.h"
 #include "mozilla/dom/Headers.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseWorkerProxy.h"
@@ -37,17 +38,16 @@
 
 #include "InternalRequest.h"
 #include "InternalResponse.h"
 
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
 #include "WorkerScope.h"
 #include "Workers.h"
-#include "FetchUtil.h"
 
 namespace mozilla {
 namespace dom {
 
 using namespace workers;
 
 class WorkerFetchResolver final : public FetchDriverObserver
 {
@@ -999,63 +999,63 @@ FetchBody<Derived>::ContinueConsumeBody(
   jsapi.Init(DerivedClass()->GetParentObject());
   JSContext* cx = jsapi.cx();
 
   ErrorResult error;
 
   switch (mConsumeType) {
     case CONSUME_ARRAYBUFFER: {
       JS::Rooted<JSObject*> arrayBuffer(cx);
-      FetchUtil::ConsumeArrayBuffer(cx, &arrayBuffer, aResultLength, aResult,
+      BodyUtil::ConsumeArrayBuffer(cx, &arrayBuffer, aResultLength, aResult,
                                     error);
 
       if (!error.Failed()) {
         JS::Rooted<JS::Value> val(cx);
         val.setObjectOrNull(arrayBuffer);
 
         localPromise->MaybeResolve(cx, val);
         // ArrayBuffer takes over ownership.
         autoFree.Reset();
       }
       break;
     }
     case CONSUME_BLOB: {
-      RefPtr<dom::Blob> blob = FetchUtil::ConsumeBlob(
+      RefPtr<dom::Blob> blob = BodyUtil::ConsumeBlob(
         DerivedClass()->GetParentObject(), NS_ConvertUTF8toUTF16(mMimeType),
         aResultLength, aResult, error);
       if (!error.Failed()) {
         localPromise->MaybeResolve(blob);
         // File takes over ownership.
         autoFree.Reset();
       }
       break;
     }
     case CONSUME_FORMDATA: {
       nsCString data;
       data.Adopt(reinterpret_cast<char*>(aResult), aResultLength);
       autoFree.Reset();
 
-      RefPtr<dom::FormData> fd = FetchUtil::ConsumeFormData(
+      RefPtr<dom::FormData> fd = BodyUtil::ConsumeFormData(
         DerivedClass()->GetParentObject(),
         mMimeType, data, error);
       if (!error.Failed()) {
         localPromise->MaybeResolve(fd);
       }
       break;
     }
     case CONSUME_TEXT:
       // fall through handles early exit.
     case CONSUME_JSON: {
       nsString decoded;
-      if (NS_SUCCEEDED(FetchUtil::ConsumeText(aResultLength, aResult, decoded))) {
+      if (NS_SUCCEEDED(BodyUtil::ConsumeText(aResultLength, aResult, decoded))) {
         if (mConsumeType == CONSUME_TEXT) {
           localPromise->MaybeResolve(decoded);
         } else {
           JS::Rooted<JS::Value> json(cx);
-          FetchUtil::ConsumeJson(cx, &json, decoded, error);
+          BodyUtil::ConsumeJson(cx, &json, decoded, error);
           if (!error.Failed()) {
             localPromise->MaybeResolve(cx, json);
           }
         }
       };
       break;
     }
     default:
--- a/dom/fetch/FetchUtil.cpp
+++ b/dom/fetch/FetchUtil.cpp
@@ -4,65 +4,16 @@
 #include "nsIUnicodeDecoder.h"
 #include "nsString.h"
 
 #include "mozilla/dom/EncodingUtils.h"
 
 namespace mozilla {
 namespace dom {
 
-namespace {
-class StreamDecoder final
-{
-  nsCOMPtr<nsIUnicodeDecoder> mDecoder;
-  nsString mDecoded;
-
-public:
-  StreamDecoder()
-    : mDecoder(EncodingUtils::DecoderForEncoding("UTF-8"))
-  {
-    MOZ_ASSERT(mDecoder);
-  }
-
-  nsresult
-  AppendText(const char* aSrcBuffer, uint32_t aSrcBufferLen)
-  {
-    int32_t destBufferLen;
-    nsresult rv =
-      mDecoder->GetMaxLength(aSrcBuffer, aSrcBufferLen, &destBufferLen);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    if (!mDecoded.SetCapacity(mDecoded.Length() + destBufferLen, fallible)) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    char16_t* destBuffer = mDecoded.BeginWriting() + mDecoded.Length();
-    int32_t totalChars = mDecoded.Length();
-
-    int32_t srcLen = (int32_t) aSrcBufferLen;
-    int32_t outLen = destBufferLen;
-    rv = mDecoder->Convert(aSrcBuffer, &srcLen, destBuffer, &outLen);
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
-
-    totalChars += outLen;
-    mDecoded.SetLength(totalChars);
-
-    return NS_OK;
-  }
-
-  nsString&
-  GetText()
-  {
-    return mDecoded;
-  }
-};
-}
-
 // static
 nsresult
 FetchUtil::GetValidRequestMethod(const nsACString& aMethod, nsCString& outMethod)
 {
   nsAutoCString upperCaseMethod(aMethod);
   ToUpperCase(upperCaseMethod);
   if (upperCaseMethod.EqualsLiteral("CONNECT") ||
       upperCaseMethod.EqualsLiteral("TRACE") ||
@@ -81,52 +32,16 @@ FetchUtil::GetValidRequestMethod(const n
     outMethod = upperCaseMethod;
   }
   else {
     outMethod = aMethod; // Case unchanged for non-standard methods
   }
   return NS_OK;
 }
 
-// static
-void
-FetchUtil::ConsumeArrayBuffer(JSContext* aCx,
-                              JS::MutableHandle<JSObject*> aValue,
-                              uint32_t aInputLength, uint8_t* aInput,
-                              ErrorResult& aRv)
-{
-  JS::Rooted<JSObject*> arrayBuffer(aCx);
-  arrayBuffer = JS_NewArrayBufferWithContents(aCx, aInputLength,
-    reinterpret_cast<void *>(aInput));
-  if (!arrayBuffer) {
-    JS_ClearPendingException(aCx);
-    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
-    return;
-  }
-  aValue.set(arrayBuffer);
-}
-
-// static
-already_AddRefed<Blob>
-FetchUtil::ConsumeBlob(nsISupports* aParent, const nsString& aMimeType,
-                       uint32_t aInputLength, uint8_t* aInput,
-                       ErrorResult& aRv)
-{
-  RefPtr<Blob> blob =
-    Blob::CreateMemoryBlob(aParent,
-                           reinterpret_cast<void *>(aInput), aInputLength,
-                           aMimeType);
-
-  if (!blob) {
-    aRv.Throw(NS_ERROR_DOM_UNKNOWN_ERR);
-    return nullptr;
-  }
-  return blob.forget();
-}
-
 static bool
 FindCRLF(nsACString::const_iterator& aStart,
          nsACString::const_iterator& aEnd)
 {
   nsACString::const_iterator end(aEnd);
   return FindInReadable(NS_LITERAL_CSTRING("\r\n"), aStart, end);
 }
 
@@ -184,455 +99,10 @@ FetchUtil::ExtractHeader(nsACString::con
   if (!NS_IsReasonableHTTPHeaderValue(aHeaderValue)) {
     return false;
   }
   aHeaderValue.CompressWhitespace();
 
   return PushOverLine(aStart);
 }
 
-namespace {
-class MOZ_STACK_CLASS FillFormIterator final
-  : public URLSearchParams::ForEachIterator
-{
-public:
-  explicit FillFormIterator(FormData* aFormData)
-    : mFormData(aFormData)
-  {
-    MOZ_ASSERT(aFormData);
-  }
-
-  bool URLParamsIterator(const nsString& aName,
-                         const nsString& aValue) override
-  {
-    ErrorResult rv;
-    mFormData->Append(aName, aValue, rv);
-    MOZ_ASSERT(!rv.Failed());
-    return true;
-  }
-
-private:
-  FormData* mFormData;
-};
-
-/**
- * A simple multipart/form-data parser as defined in RFC 2388 and RFC 2046.
- * This does not respect any encoding specified per entry, using UTF-8
- * throughout. This is as the Fetch spec states in the consume body algorithm.
- * Borrows some things from Necko's nsMultiMixedConv, but is simpler since
- * unlike Necko we do not have to deal with receiving incomplete chunks of data.
- *
- * This parser will fail the entire parse on any invalid entry, so it will
- * never return a partially filled FormData.
- * The content-disposition header is used to figure out the name and filename
- * entries. The inclusion of the filename parameter decides if the entry is
- * inserted into the FormData as a string or a File.
- *
- * File blobs are copies of the underlying data string since we cannot adopt
- * char* chunks embedded within the larger body without significant effort.
- * FIXME(nsm): Bug 1127552 - We should add telemetry to calls to formData() and
- * friends to figure out if Fetch ends up copying big blobs to see if this is
- * worth optimizing.
- */
-class MOZ_STACK_CLASS FormDataParser
-{
-private:
-  RefPtr<FormData> mFormData;
-  nsCString mMimeType;
-  nsCString mData;
-
-  // Entry state, reset in START_PART.
-  nsCString mName;
-  nsCString mFilename;
-  nsCString mContentType;
-
-  enum
-  {
-    START_PART,
-    PARSE_HEADER,
-    PARSE_BODY,
-  } mState;
-
-  nsIGlobalObject* mParentObject;
-
-  // Reads over a boundary and sets start to the position after the end of the
-  // boundary. Returns false if no boundary is found immediately.
-  bool
-  PushOverBoundary(const nsACString& aBoundaryString,
-                   nsACString::const_iterator& aStart,
-                   nsACString::const_iterator& aEnd)
-  {
-    // We copy the end iterator to keep the original pointing to the real end
-    // of the string.
-    nsACString::const_iterator end(aEnd);
-    const char* beginning = aStart.get();
-    if (FindInReadable(aBoundaryString, aStart, end)) {
-      // We either should find the body immediately, or after 2 chars with the
-      // 2 chars being '-', everything else is failure.
-      if ((aStart.get() - beginning) == 0) {
-        aStart.advance(aBoundaryString.Length());
-        return true;
-      }
-
-      if ((aStart.get() - beginning) == 2) {
-        if (*(--aStart) == '-' && *(--aStart) == '-') {
-          aStart.advance(aBoundaryString.Length() + 2);
-          return true;
-        }
-      }
-    }
-
-    return false;
-  }
-
-  bool
-  ParseHeader(nsACString::const_iterator& aStart,
-              nsACString::const_iterator& aEnd,
-              bool* aWasEmptyHeader)
-  {
-    nsAutoCString headerName, headerValue;
-    if (!FetchUtil::ExtractHeader(aStart, aEnd,
-                                  headerName, headerValue,
-                                  aWasEmptyHeader)) {
-      return false;
-    }
-    if (*aWasEmptyHeader) {
-      return true;
-    }
-
-    if (headerName.LowerCaseEqualsLiteral("content-disposition")) {
-      nsCCharSeparatedTokenizer tokenizer(headerValue, ';');
-      bool seenFormData = false;
-      while (tokenizer.hasMoreTokens()) {
-        const nsDependentCSubstring& token = tokenizer.nextToken();
-        if (token.IsEmpty()) {
-          continue;
-        }
-
-        if (token.EqualsLiteral("form-data")) {
-          seenFormData = true;
-          continue;
-        }
-
-        if (seenFormData &&
-            StringBeginsWith(token, NS_LITERAL_CSTRING("name="))) {
-          mName = StringTail(token, token.Length() - 5);
-          mName.Trim(" \"");
-          continue;
-        }
-
-        if (seenFormData &&
-            StringBeginsWith(token, NS_LITERAL_CSTRING("filename="))) {
-          mFilename = StringTail(token, token.Length() - 9);
-          mFilename.Trim(" \"");
-          continue;
-        }
-      }
-
-      if (mName.IsVoid()) {
-        // Could not parse a valid entry name.
-        return false;
-      }
-    } else if (headerName.LowerCaseEqualsLiteral("content-type")) {
-      mContentType = headerValue;
-    }
-
-    return true;
-  }
-
-  // The end of a body is marked by a CRLF followed by the boundary. So the
-  // CRLF is part of the boundary and not the body, but any prior CRLFs are
-  // part of the body. This will position the iterator at the beginning of the
-  // boundary (after the CRLF).
-  bool
-  ParseBody(const nsACString& aBoundaryString,
-            nsACString::const_iterator& aStart,
-            nsACString::const_iterator& aEnd)
-  {
-    const char* beginning = aStart.get();
-
-    // Find the boundary marking the end of the body.
-    nsACString::const_iterator end(aEnd);
-    if (!FindInReadable(aBoundaryString, aStart, end)) {
-      return false;
-    }
-
-    // We found a boundary, strip the just prior CRLF, and consider
-    // everything else the body section.
-    if (aStart.get() - beginning < 2) {
-      // Only the first entry can have a boundary right at the beginning. Even
-      // an empty body will have a CRLF before the boundary. So this is
-      // a failure.
-      return false;
-    }
-
-    // Check that there is a CRLF right before the boundary.
-    aStart.advance(-2);
-
-    // Skip optional hyphens.
-    if (*aStart == '-' && *(aStart.get()+1) == '-') {
-      if (aStart.get() - beginning < 2) {
-        return false;
-      }
-
-      aStart.advance(-2);
-    }
-
-    if (*aStart != nsCRT::CR || *(aStart.get()+1) != nsCRT::LF) {
-      return false;
-    }
-
-    nsAutoCString body(beginning, aStart.get() - beginning);
-
-    // Restore iterator to after the \r\n as we promised.
-    // We do not need to handle the extra hyphens case since our boundary
-    // parser in PushOverBoundary()
-    aStart.advance(2);
-
-    if (!mFormData) {
-      mFormData = new FormData();
-    }
-
-    NS_ConvertUTF8toUTF16 name(mName);
-
-    if (mFilename.IsVoid()) {
-      ErrorResult rv;
-      mFormData->Append(name, NS_ConvertUTF8toUTF16(body), rv);
-      MOZ_ASSERT(!rv.Failed());
-    } else {
-      // Unfortunately we've to copy the data first since all our strings are
-      // going to free it. We also need fallible alloc, so we can't just use
-      // ToNewCString().
-      char* copy = static_cast<char*>(moz_xmalloc(body.Length()));
-      if (!copy) {
-        NS_WARNING("Failed to copy File entry body.");
-        return false;
-      }
-      nsCString::const_iterator bodyIter, bodyEnd;
-      body.BeginReading(bodyIter);
-      body.EndReading(bodyEnd);
-      char *p = copy;
-      while (bodyIter != bodyEnd) {
-        *p++ = *bodyIter++;
-      }
-      p = nullptr;
-
-      RefPtr<Blob> file =
-        File::CreateMemoryFile(mParentObject,
-                               reinterpret_cast<void *>(copy), body.Length(),
-                               NS_ConvertUTF8toUTF16(mFilename),
-                               NS_ConvertUTF8toUTF16(mContentType), /* aLastModifiedDate */ 0);
-      Optional<nsAString> dummy;
-      ErrorResult rv;
-      mFormData->Append(name, *file, dummy, rv);
-      if (NS_WARN_IF(rv.Failed())) {
-        return false;
-      }
-    }
-
-    return true;
-  }
-
-public:
-  FormDataParser(const nsACString& aMimeType, const nsACString& aData, nsIGlobalObject* aParent)
-    : mMimeType(aMimeType), mData(aData), mState(START_PART), mParentObject(aParent)
-  {
-  }
-
-  bool
-  Parse()
-  {
-    // Determine boundary from mimetype.
-    const char* boundaryId = nullptr;
-    boundaryId = strstr(mMimeType.BeginWriting(), "boundary");
-    if (!boundaryId) {
-      return false;
-    }
-
-    boundaryId = strchr(boundaryId, '=');
-    if (!boundaryId) {
-      return false;
-    }
-
-    // Skip over '='.
-    boundaryId++;
-
-    char *attrib = (char *) strchr(boundaryId, ';');
-    if (attrib) *attrib = '\0';
-
-    nsAutoCString boundaryString(boundaryId);
-    if (attrib) *attrib = ';';
-
-    boundaryString.Trim(" \"");
-
-    if (boundaryString.Length() == 0) {
-      return false;
-    }
-
-    nsACString::const_iterator start, end;
-    mData.BeginReading(start);
-    // This should ALWAYS point to the end of data.
-    // Helpers make copies.
-    mData.EndReading(end);
-
-    while (start != end) {
-      switch(mState) {
-        case START_PART:
-          mName.SetIsVoid(true);
-          mFilename.SetIsVoid(true);
-          mContentType = NS_LITERAL_CSTRING("text/plain");
-
-          // MUST start with boundary.
-          if (!PushOverBoundary(boundaryString, start, end)) {
-            return false;
-          }
-
-          if (start != end && *start == '-') {
-            // End of data.
-            if (!mFormData) {
-              mFormData = new FormData();
-            }
-            return true;
-          }
-
-          if (!PushOverLine(start)) {
-            return false;
-          }
-          mState = PARSE_HEADER;
-          break;
-
-        case PARSE_HEADER:
-          bool emptyHeader;
-          if (!ParseHeader(start, end, &emptyHeader)) {
-            return false;
-          }
-
-          if (emptyHeader && !PushOverLine(start)) {
-            return false;
-          }
-
-          mState = emptyHeader ? PARSE_BODY : PARSE_HEADER;
-          break;
-
-        case PARSE_BODY:
-          if (mName.IsVoid()) {
-            NS_WARNING("No content-disposition header with a valid name was "
-                       "found. Failing at body parse.");
-            return false;
-          }
-
-          if (!ParseBody(boundaryString, start, end)) {
-            return false;
-          }
-
-          mState = START_PART;
-          break;
-
-        default:
-          MOZ_CRASH("Invalid case");
-      }
-    }
-
-    NS_NOTREACHED("Should never reach here.");
-    return false;
-  }
-
-  already_AddRefed<FormData> GetFormData()
-  {
-    return mFormData.forget();
-  }
-};
-}
-
-// static
-already_AddRefed<FormData>
-FetchUtil::ConsumeFormData(nsIGlobalObject* aParent, const nsCString& aMimeType,
-                           const nsCString& aStr, ErrorResult& aRv)
-{
-  NS_NAMED_LITERAL_CSTRING(formDataMimeType, "multipart/form-data");
-
-  // Allow semicolon separated boundary/encoding suffix like multipart/form-data; boundary=
-  // but disallow multipart/form-datafoobar.
-  bool isValidFormDataMimeType = StringBeginsWith(aMimeType, formDataMimeType);
-
-  if (isValidFormDataMimeType && aMimeType.Length() > formDataMimeType.Length()) {
-    isValidFormDataMimeType = aMimeType[formDataMimeType.Length()] == ';';
-  }
-
-  if (isValidFormDataMimeType) {
-    FormDataParser parser(aMimeType, aStr, aParent);
-    if (!parser.Parse()) {
-      aRv.ThrowTypeError<MSG_BAD_FORMDATA>();
-      return nullptr;
-    }
-
-    RefPtr<FormData> fd = parser.GetFormData();
-    MOZ_ASSERT(fd);
-    return fd.forget();
-  }
-
-  NS_NAMED_LITERAL_CSTRING(urlDataMimeType, "application/x-www-form-urlencoded");
-  bool isValidUrlEncodedMimeType = StringBeginsWith(aMimeType, urlDataMimeType);
-
-  if (isValidUrlEncodedMimeType && aMimeType.Length() > urlDataMimeType.Length()) {
-    isValidUrlEncodedMimeType = aMimeType[urlDataMimeType.Length()] == ';';
-  }
-
-  if (isValidUrlEncodedMimeType) {
-    URLParams params;
-    params.ParseInput(aStr);
-
-    RefPtr<FormData> fd = new FormData(aParent);
-    FillFormIterator iterator(fd);
-    DebugOnly<bool> status = params.ForEach(iterator);
-    MOZ_ASSERT(status);
-
-    return fd.forget();
-  }
-
-  aRv.ThrowTypeError<MSG_BAD_FORMDATA>();
-  return nullptr;
-}
-
-// static
-nsresult
-FetchUtil::ConsumeText(uint32_t aInputLength, uint8_t* aInput,
-                       nsString& aText)
-{
-  StreamDecoder decoder;
-  nsresult rv = decoder.AppendText(reinterpret_cast<char*>(aInput),
-                                   aInputLength);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-  aText = decoder.GetText();
-  return NS_OK;
-}
-
-// static
-void
-FetchUtil::ConsumeJson(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
-                       const nsString& aStr, ErrorResult& aRv)
-{
-  aRv.MightThrowJSException();
-
-  AutoForceSetExceptionOnContext forceExn(aCx);
-  JS::Rooted<JS::Value> json(aCx);
-  if (!JS_ParseJSON(aCx, aStr.get(), aStr.Length(), &json)) {
-    if (!JS_IsExceptionPending(aCx)) {
-      aRv.Throw(NS_ERROR_DOM_UNKNOWN_ERR);
-      return;
-    }
-
-    JS::Rooted<JS::Value> exn(aCx);
-    DebugOnly<bool> gotException = JS_GetPendingException(aCx, &exn);
-    MOZ_ASSERT(gotException);
-
-    JS_ClearPendingException(aCx);
-    aRv.ThrowJSException(aCx, exn);
-    return;
-  }
-
-  aValue.set(json);
-}
-
 } // namespace dom
 } // namespace mozilla
--- a/dom/fetch/FetchUtil.h
+++ b/dom/fetch/FetchUtil.h
@@ -22,56 +22,16 @@ public:
   * Implements checks and normalization as specified by the Fetch specification.
   * Returns NS_ERROR_DOM_SECURITY_ERR if the method is invalid.
   * Otherwise returns NS_OK and the normalized method via outMethod.
   */
   static nsresult
   GetValidRequestMethod(const nsACString& aMethod, nsCString& outMethod);
 
   /**
-   * Creates an array buffer from an array, assigning the result to |aValue|.
-   * The array buffer takes ownership of |aInput|, which must be allocated
-   * by |malloc|.
-   */
-  static void
-  ConsumeArrayBuffer(JSContext* aCx, JS::MutableHandle<JSObject*> aValue,
-                     uint32_t aInputLength, uint8_t* aInput, ErrorResult& aRv);
-
-  /**
-   * Creates an in-memory blob from an array. The blob takes ownership of
-   * |aInput|, which must be allocated by |malloc|.
-   */
-  static already_AddRefed<Blob>
-  ConsumeBlob(nsISupports* aParent, const nsString& aMimeType,
-              uint32_t aInputLength, uint8_t* aInput, ErrorResult& aRv);
-
-  /**
-   * Creates a form data object from a UTF-8 encoded |aStr|. Returns |nullptr|
-   * and sets |aRv| to MSG_BAD_FORMDATA if |aStr| contains invalid data.
-   */
-  static already_AddRefed<FormData>
-  ConsumeFormData(nsIGlobalObject* aParent, const nsCString& aMimeType,
-                  const nsCString& aStr, ErrorResult& aRv);
-
-  /**
-   * UTF-8 decodes |aInput| into |aText|. The caller may free |aInput|
-   * once this method returns.
-   */
-  static nsresult
-  ConsumeText(uint32_t aInputLength, uint8_t* aInput, nsString& aText);
-
-  /**
-   * Parses a UTF-8 encoded |aStr| as JSON, assigning the result to |aValue|.
-   * Sets |aRv| to a syntax error if |aStr| contains invalid data.
-   */
-  static void
-  ConsumeJson(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
-              const nsString& aStr, ErrorResult& aRv);
-
-  /**
    * Extracts an HTTP header from a substring range.
    */
   static bool
   ExtractHeader(nsACString::const_iterator& aStart,
                 nsACString::const_iterator& aEnd,
                 nsCString& aHeaderName,
                 nsCString& aHeaderValue,
                 bool* aWasEmptyHeader);
--- a/dom/push/PushNotifier.cpp
+++ b/dom/push/PushNotifier.cpp
@@ -8,18 +8,18 @@
 #include "nsCOMPtr.h"
 #include "nsXPCOM.h"
 #include "nsIXULRuntime.h"
 #include "ServiceWorkerManager.h"
 
 #include "mozilla/Services.h"
 #include "mozilla/unused.h"
 
+#include "mozilla/dom/BodyUtil.h"
 #include "mozilla/dom/ContentParent.h"
-#include "mozilla/dom/FetchUtil.h"
 
 namespace mozilla {
 namespace dom {
 
 using workers::ServiceWorkerManager;
 
 PushNotifier::PushNotifier()
 {}
@@ -245,17 +245,17 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(PushMess
 NS_IMPL_CYCLE_COLLECTING_RELEASE(PushMessage)
 
 nsresult
 PushMessage::EnsureDecodedText()
 {
   if (mData.IsEmpty() || !mDecodedText.IsEmpty()) {
     return NS_OK;
   }
-  nsresult rv = FetchUtil::ConsumeText(
+  nsresult rv = BodyUtil::ConsumeText(
     mData.Length(),
     reinterpret_cast<uint8_t*>(mData.Elements()),
     mDecodedText
   );
   if (NS_WARN_IF(NS_FAILED(rv))) {
     mDecodedText.Truncate();
     return rv;
   }
@@ -277,17 +277,17 @@ NS_IMETHODIMP
 PushMessage::Json(JSContext* aCx,
                   JS::MutableHandle<JS::Value> aResult)
 {
   nsresult rv = EnsureDecodedText();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   ErrorResult error;
-  FetchUtil::ConsumeJson(aCx, aResult, mDecodedText, error);
+  BodyUtil::ConsumeJson(aCx, aResult, mDecodedText, error);
   if (error.Failed()) {
     return error.StealNSResult();
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PushMessage::Binary(uint32_t* aDataLen, uint8_t** aData)
--- a/dom/workers/ServiceWorkerEvents.cpp
+++ b/dom/workers/ServiceWorkerEvents.cpp
@@ -32,18 +32,18 @@
 #include "mozilla/dom/Response.h"
 #include "mozilla/dom/WorkerScope.h"
 #include "mozilla/dom/workers/bindings/ServiceWorker.h"
 
 #ifndef MOZ_SIMPLEPUSH
 #include "nsIUnicodeDecoder.h"
 #include "nsIUnicodeEncoder.h"
 
+#include "mozilla/dom/BodyUtil.h"
 #include "mozilla/dom/EncodingUtils.h"
-#include "mozilla/dom/FetchUtil.h"
 #include "mozilla/dom/TypedArray.h"
 #endif
 
 #include "js/Conversions.h"
 #include "js/TypeDecls.h"
 #include "WorkerPrivate.h"
 #include "xpcpublic.h"
 
@@ -1007,17 +1007,17 @@ NS_INTERFACE_MAP_END
 void
 PushMessageData::Json(JSContext* cx, JS::MutableHandle<JS::Value> aRetval,
                       ErrorResult& aRv)
 {
   if (NS_FAILED(EnsureDecodedText())) {
     aRv.Throw(NS_ERROR_DOM_UNKNOWN_ERR);
     return;
   }
-  FetchUtil::ConsumeJson(cx, aRetval, mDecodedText, aRv);
+  BodyUtil::ConsumeJson(cx, aRetval, mDecodedText, aRv);
 }
 
 void
 PushMessageData::Text(nsAString& aData)
 {
   if (NS_SUCCEEDED(EnsureDecodedText())) {
     aData = mDecodedText;
   }
@@ -1025,41 +1025,41 @@ PushMessageData::Text(nsAString& aData)
 
 void
 PushMessageData::ArrayBuffer(JSContext* cx,
                              JS::MutableHandle<JSObject*> aRetval,
                              ErrorResult& aRv)
 {
   uint8_t* data = GetContentsCopy();
   if (data) {
-    FetchUtil::ConsumeArrayBuffer(cx, aRetval, mBytes.Length(), data, aRv);
+    BodyUtil::ConsumeArrayBuffer(cx, aRetval, mBytes.Length(), data, aRv);
   }
 }
 
 already_AddRefed<mozilla::dom::Blob>
 PushMessageData::Blob(ErrorResult& aRv)
 {
   uint8_t* data = GetContentsCopy();
   if (data) {
-    RefPtr<mozilla::dom::Blob> blob = FetchUtil::ConsumeBlob(
+    RefPtr<mozilla::dom::Blob> blob = BodyUtil::ConsumeBlob(
       mOwner, EmptyString(), mBytes.Length(), data, aRv);
     if (blob) {
       return blob.forget();
     }
   }
   return nullptr;
 }
 
 NS_METHOD
 PushMessageData::EnsureDecodedText()
 {
   if (mBytes.IsEmpty() || !mDecodedText.IsEmpty()) {
     return NS_OK;
   }
-  nsresult rv = FetchUtil::ConsumeText(
+  nsresult rv = BodyUtil::ConsumeText(
     mBytes.Length(),
     reinterpret_cast<uint8_t*>(mBytes.Elements()),
     mDecodedText
   );
   if (NS_WARN_IF(NS_FAILED(rv))) {
     mDecodedText.Truncate();
     return rv;
   }